Imported Upstream version 3.6 upstream upstream/3.6
authorPatrick McCarty <patrick.mccarty@linux.intel.com>
Wed, 6 Mar 2013 19:08:23 +0000 (11:08 -0800)
committerPatrick McCarty <patrick.mccarty@linux.intel.com>
Wed, 6 Mar 2013 19:08:23 +0000 (11:08 -0800)
239 files changed:
AUTHORS.txt [new file with mode: 0644]
CHANGES.txt [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README.txt [new file with mode: 0644]
coverage.egg-info/PKG-INFO [new file with mode: 0644]
coverage.egg-info/SOURCES.txt [new file with mode: 0644]
coverage.egg-info/dependency_links.txt [new file with mode: 0644]
coverage.egg-info/entry_points.txt [new file with mode: 0644]
coverage.egg-info/not-zip-safe [new file with mode: 0644]
coverage.egg-info/top_level.txt [new file with mode: 0644]
coverage/__init__.py [new file with mode: 0644]
coverage/__main__.py [new file with mode: 0644]
coverage/annotate.py [new file with mode: 0644]
coverage/backward.py [new file with mode: 0644]
coverage/bytecode.py [new file with mode: 0644]
coverage/cmdline.py [new file with mode: 0644]
coverage/codeunit.py [new file with mode: 0644]
coverage/collector.py [new file with mode: 0644]
coverage/config.py [new file with mode: 0644]
coverage/control.py [new file with mode: 0644]
coverage/data.py [new file with mode: 0644]
coverage/execfile.py [new file with mode: 0644]
coverage/files.py [new file with mode: 0644]
coverage/fullcoverage/encodings.py [new file with mode: 0644]
coverage/html.py [new file with mode: 0644]
coverage/htmlfiles/coverage_html.js [new file with mode: 0644]
coverage/htmlfiles/index.html [new file with mode: 0644]
coverage/htmlfiles/jquery-1.4.3.min.js [new file with mode: 0644]
coverage/htmlfiles/jquery.hotkeys.js [new file with mode: 0644]
coverage/htmlfiles/jquery.isonscreen.js [new file with mode: 0644]
coverage/htmlfiles/jquery.tablesorter.min.js [new file with mode: 0644]
coverage/htmlfiles/keybd_closed.png [new file with mode: 0644]
coverage/htmlfiles/keybd_open.png [new file with mode: 0644]
coverage/htmlfiles/pyfile.html [new file with mode: 0644]
coverage/htmlfiles/style.css [new file with mode: 0644]
coverage/misc.py [new file with mode: 0644]
coverage/parser.py [new file with mode: 0644]
coverage/phystokens.py [new file with mode: 0644]
coverage/report.py [new file with mode: 0644]
coverage/results.py [new file with mode: 0644]
coverage/summary.py [new file with mode: 0644]
coverage/templite.py [new file with mode: 0644]
coverage/tracer.c [new file with mode: 0644]
coverage/version.py [new file with mode: 0644]
coverage/xmlreport.py [new file with mode: 0644]
doc/api.rst [new file with mode: 0644]
doc/branch.rst [new file with mode: 0644]
doc/changes.rst [new file with mode: 0644]
doc/cmd.rst [new file with mode: 0644]
doc/config.rst [new file with mode: 0644]
doc/contributing.rst [new file with mode: 0644]
doc/excluding.rst [new file with mode: 0644]
doc/faq.rst [new file with mode: 0644]
doc/index.rst [new file with mode: 0644]
doc/install.rst [new file with mode: 0644]
doc/source.rst [new file with mode: 0644]
doc/subprocess.rst [new file with mode: 0644]
doc/trouble.rst [new file with mode: 0644]
igor.py [new file with mode: 0644]
requirements.txt [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
test/__init__.py [new file with mode: 0644]
test/backtest.py [new file with mode: 0644]
test/backunittest.py [new file with mode: 0644]
test/coveragetest.py [new file with mode: 0644]
test/covmodzip1.py [new file with mode: 0644]
test/eggsrc/egg1/__init__.py [new file with mode: 0644]
test/eggsrc/egg1/egg1.py [new file with mode: 0644]
test/eggsrc/setup.py [new file with mode: 0644]
test/farm/annotate/annotate_dir.py [new file with mode: 0644]
test/farm/annotate/gold/white.py,cover [new file with mode: 0644]
test/farm/annotate/gold_anno_dir/a___init__.py,cover [new file with mode: 0644]
test/farm/annotate/gold_anno_dir/a_a.py,cover [new file with mode: 0644]
test/farm/annotate/gold_anno_dir/b___init__.py,cover [new file with mode: 0644]
test/farm/annotate/gold_anno_dir/b_b.py,cover [new file with mode: 0644]
test/farm/annotate/gold_anno_dir/multi.py,cover [new file with mode: 0644]
test/farm/annotate/gold_multi/a/__init__.py,cover [new file with mode: 0644]
test/farm/annotate/gold_multi/a/a.py,cover [new file with mode: 0644]
test/farm/annotate/gold_multi/b/__init__.py,cover [new file with mode: 0644]
test/farm/annotate/gold_multi/b/b.py,cover [new file with mode: 0644]
test/farm/annotate/gold_multi/multi.py,cover [new file with mode: 0644]
test/farm/annotate/gold_v24/white.py,cover [new file with mode: 0644]
test/farm/annotate/run.py [new file with mode: 0644]
test/farm/annotate/run_multi.py [new file with mode: 0644]
test/farm/annotate/src/a/__init__.py [new file with mode: 0644]
test/farm/annotate/src/a/a.py [new file with mode: 0644]
test/farm/annotate/src/b/__init__.py [new file with mode: 0644]
test/farm/annotate/src/b/b.py [new file with mode: 0644]
test/farm/annotate/src/multi.py [new file with mode: 0644]
test/farm/annotate/src/white.py [new file with mode: 0644]
test/farm/html/gold_a/a.html [new file with mode: 0644]
test/farm/html/gold_a/index.html [new file with mode: 0644]
test/farm/html/gold_b_branch/b.html [new file with mode: 0644]
test/farm/html/gold_b_branch/index.html [new file with mode: 0644]
test/farm/html/gold_bom/bom.html [new file with mode: 0644]
test/farm/html/gold_bom/index.html [new file with mode: 0644]
test/farm/html/gold_isolatin1/index.html [new file with mode: 0644]
test/farm/html/gold_isolatin1/isolatin1.html [new file with mode: 0644]
test/farm/html/gold_omit_1/index.html [new file with mode: 0644]
test/farm/html/gold_omit_1/m1.html [new file with mode: 0644]
test/farm/html/gold_omit_1/m2.html [new file with mode: 0644]
test/farm/html/gold_omit_1/m3.html [new file with mode: 0644]
test/farm/html/gold_omit_1/main.html [new file with mode: 0644]
test/farm/html/gold_omit_2/index.html [new file with mode: 0644]
test/farm/html/gold_omit_2/m2.html [new file with mode: 0644]
test/farm/html/gold_omit_2/m3.html [new file with mode: 0644]
test/farm/html/gold_omit_2/main.html [new file with mode: 0644]
test/farm/html/gold_omit_3/index.html [new file with mode: 0644]
test/farm/html/gold_omit_3/m3.html [new file with mode: 0644]
test/farm/html/gold_omit_3/main.html [new file with mode: 0644]
test/farm/html/gold_omit_4/index.html [new file with mode: 0644]
test/farm/html/gold_omit_4/m1.html [new file with mode: 0644]
test/farm/html/gold_omit_4/m3.html [new file with mode: 0644]
test/farm/html/gold_omit_4/main.html [new file with mode: 0644]
test/farm/html/gold_omit_5/index.html [new file with mode: 0644]
test/farm/html/gold_omit_5/m1.html [new file with mode: 0644]
test/farm/html/gold_omit_5/main.html [new file with mode: 0644]
test/farm/html/gold_other/blah_blah_other.html [new file with mode: 0644]
test/farm/html/gold_other/here.html [new file with mode: 0644]
test/farm/html/gold_other/index.html [new file with mode: 0644]
test/farm/html/gold_partial/index.html [new file with mode: 0644]
test/farm/html/gold_partial/partial.html [new file with mode: 0644]
test/farm/html/gold_styled/a.html [new file with mode: 0644]
test/farm/html/gold_styled/extra.css [new file with mode: 0644]
test/farm/html/gold_styled/index.html [new file with mode: 0644]
test/farm/html/gold_styled/style.css [new file with mode: 0644]
test/farm/html/gold_unicode/index.html [new file with mode: 0644]
test/farm/html/gold_unicode/unicode.html [new file with mode: 0644]
test/farm/html/gold_x_xml/coverage.xml [new file with mode: 0644]
test/farm/html/gold_y_xml_branch/coverage.xml [new file with mode: 0644]
test/farm/html/othersrc/other.py [new file with mode: 0644]
test/farm/html/run_a.py [new file with mode: 0644]
test/farm/html/run_a_xml_1.py [new file with mode: 0644]
test/farm/html/run_a_xml_2.py [new file with mode: 0644]
test/farm/html/run_b_branch.py [new file with mode: 0644]
test/farm/html/run_bom.py [new file with mode: 0644]
test/farm/html/run_isolatin1.py [new file with mode: 0644]
test/farm/html/run_omit_1.py [new file with mode: 0644]
test/farm/html/run_omit_2.py [new file with mode: 0644]
test/farm/html/run_omit_3.py [new file with mode: 0644]
test/farm/html/run_omit_4.py [new file with mode: 0644]
test/farm/html/run_omit_5.py [new file with mode: 0644]
test/farm/html/run_other.py [new file with mode: 0644]
test/farm/html/run_partial.py [new file with mode: 0644]
test/farm/html/run_styled.py [new file with mode: 0644]
test/farm/html/run_tabbed.py [new file with mode: 0644]
test/farm/html/run_unicode.py [new file with mode: 0644]
test/farm/html/run_y_xml_branch.py [new file with mode: 0644]
test/farm/html/src/a.py [new file with mode: 0644]
test/farm/html/src/b.py [new file with mode: 0644]
test/farm/html/src/bom.py [new file with mode: 0644]
test/farm/html/src/coverage.xml [new file with mode: 0644]
test/farm/html/src/extra.css [new file with mode: 0644]
test/farm/html/src/here.py [new file with mode: 0644]
test/farm/html/src/isolatin1.py [new file with mode: 0644]
test/farm/html/src/m1.py [new file with mode: 0644]
test/farm/html/src/m2.py [new file with mode: 0644]
test/farm/html/src/m3.py [new file with mode: 0644]
test/farm/html/src/main.py [new file with mode: 0644]
test/farm/html/src/omit4.ini [new file with mode: 0644]
test/farm/html/src/omit5.ini [new file with mode: 0644]
test/farm/html/src/partial.py [new file with mode: 0644]
test/farm/html/src/run_a_xml_2.ini [new file with mode: 0644]
test/farm/html/src/tabbed.py [new file with mode: 0644]
test/farm/html/src/unicode.py [new file with mode: 0644]
test/farm/html/src/y.py [new file with mode: 0644]
test/farm/run/run_chdir.py [new file with mode: 0644]
test/farm/run/run_timid.py [new file with mode: 0644]
test/farm/run/run_xxx.py [new file with mode: 0644]
test/farm/run/src/chdir.py [new file with mode: 0644]
test/farm/run/src/showtrace.py [new file with mode: 0644]
test/farm/run/src/subdir/placeholder [new file with mode: 0644]
test/farm/run/src/xxx [new file with mode: 0644]
test/js/index.html [new file with mode: 0644]
test/js/tests.js [new file with mode: 0644]
test/modules/aa/__init__.py [new file with mode: 0644]
test/modules/aa/afile.odd.py [new file with mode: 0644]
test/modules/aa/afile.py [new file with mode: 0644]
test/modules/aa/bb.odd/bfile.py [new file with mode: 0644]
test/modules/aa/bb/__init__.py [new file with mode: 0644]
test/modules/aa/bb/bfile.odd.py [new file with mode: 0644]
test/modules/aa/bb/bfile.py [new file with mode: 0644]
test/modules/aa/bb/cc/__init__.py [new file with mode: 0644]
test/modules/aa/bb/cc/cfile.py [new file with mode: 0644]
test/modules/aa/zfile.py [new file with mode: 0644]
test/modules/covmod1.py [new file with mode: 0644]
test/modules/pkg1/__init__.py [new file with mode: 0644]
test/modules/pkg1/__main__.py [new file with mode: 0644]
test/modules/pkg1/p1a.py [new file with mode: 0644]
test/modules/pkg1/p1b.py [new file with mode: 0644]
test/modules/pkg1/p1c.py [new file with mode: 0644]
test/modules/pkg1/runmod2.py [new file with mode: 0644]
test/modules/pkg1/sub/__init__.py [new file with mode: 0644]
test/modules/pkg1/sub/__main__.py [new file with mode: 0644]
test/modules/pkg1/sub/ps1a.py [new file with mode: 0644]
test/modules/pkg1/sub/runmod3.py [new file with mode: 0644]
test/modules/pkg2/__init__.py [new file with mode: 0644]
test/modules/pkg2/p2a.py [new file with mode: 0644]
test/modules/pkg2/p2b.py [new file with mode: 0644]
test/modules/runmod1.py [new file with mode: 0644]
test/modules/usepkgs.py [new file with mode: 0644]
test/moremodules/othermods/__init__.py [new file with mode: 0644]
test/moremodules/othermods/othera.py [new file with mode: 0644]
test/moremodules/othermods/otherb.py [new file with mode: 0644]
test/moremodules/othermods/sub/__init__.py [new file with mode: 0644]
test/moremodules/othermods/sub/osa.py [new file with mode: 0644]
test/moremodules/othermods/sub/osb.py [new file with mode: 0644]
test/osinfo.py [new file with mode: 0644]
test/qunit/jquery.tmpl.min.js [new file with mode: 0644]
test/qunit/qunit.css [new file with mode: 0644]
test/qunit/qunit.js [new file with mode: 0644]
test/stress_phystoken.tok [new file with mode: 0644]
test/stress_phystoken_dos.tok [new file with mode: 0644]
test/test_api.py [new file with mode: 0644]
test/test_arcs.py [new file with mode: 0644]
test/test_cmdline.py [new file with mode: 0644]
test/test_codeunit.py [new file with mode: 0644]
test/test_config.py [new file with mode: 0644]
test/test_coverage.py [new file with mode: 0644]
test/test_data.py [new file with mode: 0644]
test/test_execfile.py [new file with mode: 0644]
test/test_farm.py [new file with mode: 0644]
test/test_files.py [new file with mode: 0644]
test/test_html.py [new file with mode: 0644]
test/test_misc.py [new file with mode: 0644]
test/test_oddball.py [new file with mode: 0644]
test/test_parser.py [new file with mode: 0644]
test/test_phystokens.py [new file with mode: 0644]
test/test_process.py [new file with mode: 0644]
test/test_results.py [new file with mode: 0644]
test/test_summary.py [new file with mode: 0644]
test/test_templite.py [new file with mode: 0644]
test/test_testing.py [new file with mode: 0644]
test/test_xml.py [new file with mode: 0644]
test/try_execfile.py [new file with mode: 0644]
test_old.sh [new file with mode: 0644]
tox.ini [new file with mode: 0644]

diff --git a/AUTHORS.txt b/AUTHORS.txt
new file mode 100644 (file)
index 0000000..7fb80bd
--- /dev/null
@@ -0,0 +1,41 @@
+Coverage.py was originally written by Gareth Rees, and since 2004 has been
+extended and maintained by Ned Batchelder.
+
+Other contributions have been made by:
+
+Marc Abramowitz
+Chris Adams
+Geoff Bache
+Julian Berman
+Titus Brown
+Brett Cannon
+Pablo Carballo
+Guillaume Chazarain
+David Christian
+Marcus Cobden
+Danek Duvall
+Ben Finney
+Martin Fuzzey
+Imri Goldberg
+Bill Hart
+Christian Heimes
+Devin Jeanpierre
+Ross Lawley
+Edward Loper
+Sandra Martocchia
+Patrick Mezard
+Noel O'Boyle
+Detlev Offenbach
+JT Olds
+George Paci
+Catherine Proulx
+Brandon Rhodes
+Adi Roiban
+Greg Rogers
+George Song
+David Stanek
+Joseph Tate
+Sigve Tjora
+Mark van der Wal
+Zooko Wilcox-O'Hearn
+Christoph Zwerschke
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644 (file)
index 0000000..9404fc0
--- /dev/null
@@ -0,0 +1,943 @@
+------------------------------
+Change history for Coverage.py
+------------------------------
+
+Version 3.6 --- 5 January 2013
+------------------------------
+
+- Added a page to the docs about troublesome situations, closing `issue 226`_,
+  and added some info to the TODO file, closing `issue 227`_.
+
+.. _issue 226: https://bitbucket.org/ned/coveragepy/issue/226/make-readme-section-to-describe-when
+.. _issue 227: https://bitbucket.org/ned/coveragepy/issue/227/update-todo
+
+
+Version 3.6b3 --- 29 December 2012
+----------------------------------
+
+- Beta 2 broke the nose plugin. It's fixed again, closing `issue 224`_.
+
+.. _issue 224: https://bitbucket.org/ned/coveragepy/issue/224/36b2-breaks-nosexcover
+
+
+Version 3.6b2 --- 23 December 2012
+----------------------------------
+
+- Coverage.py runs on Python 2.3 and 2.4 again. It was broken in 3.6b1.
+
+- The C extension is optionally compiled using a different more widely-used
+  technique, taking another stab at fixing `issue 80`_ once and for all.
+
+- Combining data files would create entries for phantom files if used with
+  ``source`` and path aliases.  It no longer does.
+
+- ``debug sys`` now shows the configuration file path that was read.
+
+- If an oddly-behaved package claims that code came from an empty-string
+  filename, coverage.py no longer associates it with the directory name,
+  fixing `issue 221`_.
+
+.. _issue 80: https://bitbucket.org/ned/coveragepy/issue/80/is-there-a-duck-typing-way-to-know-we-cant
+.. _issue 221: https://bitbucket.org/ned/coveragepy/issue/221/coveragepy-incompatible-with-pyratemp
+
+
+Version 3.6b1 --- 28 November 2012
+----------------------------------
+
+- Wildcards in ``include=`` and ``omit=`` arguments were not handled properly
+  in reporting functions, though they were when running.  Now they are handled
+  uniformly, closing `issue 143`_ and `issue 163`_.  **NOTE**: it is possible
+  that your configurations may now be incorrect.  If you use ``include`` or
+  ``omit`` during reporting, whether on the command line, through the API, or
+  in a configuration file, please check carefully that you were not relying on
+  the old broken behavior.
+
+- The **report**, **html**, and **xml** commands now accept a ``--fail-under``
+  switch that indicates in the exit status whether the coverage percentage was
+  less than a particular value.  Closes `issue 139`_.
+
+- The reporting functions coverage.report(), coverage.html_report(), and
+  coverage.xml_report() now all return a float, the total percentage covered
+  measurement.
+
+- The HTML report's title can now be set in the configuration file, with the
+  ``--title`` switch on the command line, or via the API.
+
+- Configuration files now support substitution of environment variables, using
+  syntax like ``${WORD}``.  Closes `issue 97`_.
+
+- Embarrassingly, the `[xml] output=` setting in the .coveragerc file simply
+  didn't work.  Now it does.
+
+- The XML report now consistently uses filenames for the filename attribute,
+  rather than sometimes using module names.  Fixes `issue 67`_.
+  Thanks, Marcus Cobden.
+
+- Coverage percentage metrics are now computed slightly differently under
+  branch coverage.  This means that completely unexecuted files will now
+  correctly have 0% coverage, fixing `issue 156`_.  This also means that your
+  total coverage numbers will generally now be lower if you are measuring
+  branch coverage.
+
+- When installing, now in addition to creating a "coverage" command, two new
+  aliases are also installed.  A "coverage2" or "coverage3" command will be
+  created, depending on whether you are installing in Python 2.x or 3.x.
+  A "coverage-X.Y" command will also be created corresponding to your specific
+  version of Python.  Closes `issue 111`_.
+
+- The coverage.py installer no longer tries to bootstrap setuptools or
+  Distribute.  You must have one of them installed first, as `issue 202`_
+  recommended.
+
+- The coverage.py kit now includes docs (closing `issue 137`_) and tests.
+
+- On Windows, files are now reported in their correct case, fixing `issue 89`_
+  and `issue 203`_.
+
+- If a file is missing during reporting, the path shown in the error message
+  is now correct, rather than an incorrect path in the current directory.
+  Fixes `issue 60`_.
+
+- Running an HTML report in Python 3 in the same directory as an old Python 2
+  HTML report would fail with a UnicodeDecodeError. This issue (`issue 193`_)
+  is now fixed.
+
+- Fixed yet another error trying to parse non-Python files as Python, this
+  time an IndentationError, closing `issue 82`_ for the fourth time...
+
+- If `coverage xml` fails because there is no data to report, it used to
+  create a zero-length XML file.  Now it doesn't, fixing `issue 210`_.
+
+- Jython files now work with the ``--source`` option, fixing `issue 100`_.
+
+- Running coverage under a debugger is unlikely to work, but it shouldn't fail
+  with "TypeError: 'NoneType' object is not iterable".  Fixes `issue 201`_.
+
+- On some Linux distributions, when installed with the OS package manager,
+  coverage.py would report its own code as part of the results.  Now it won't,
+  fixing `issue 214`_, though this will take some time to be repackaged by the
+  operating systems.
+
+- Docstrings for the legacy singleton methods are more helpful.  Thanks Marius
+  Gedminas.  Closes `issue 205`_.
+
+- The pydoc tool can now show docmentation for the class `coverage.coverage`.
+  Closes `issue 206`_.
+
+- Added a page to the docs about contributing to coverage.py, closing
+  `issue 171`_.
+
+- When coverage.py ended unsuccessfully, it may have reported odd errors like
+  ``'NoneType' object has no attribute 'isabs'``.  It no longer does,
+  so kiss `issue 153`_ goodbye.
+
+.. _issue 60: https://bitbucket.org/ned/coveragepy/issue/60/incorrect-path-to-orphaned-pyc-files
+.. _issue 67: https://bitbucket.org/ned/coveragepy/issue/67/xml-report-filenames-may-be-generated
+.. _issue 82: https://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report
+.. _issue 89: https://bitbucket.org/ned/coveragepy/issue/89/on-windows-all-packages-are-reported-in
+.. _issue 97: https://bitbucket.org/ned/coveragepy/issue/97/allow-environment-variables-to-be
+.. _issue 100: https://bitbucket.org/ned/coveragepy/issue/100/source-directive-doesnt-work-for-packages
+.. _issue 111: https://bitbucket.org/ned/coveragepy/issue/111/when-installing-coverage-with-pip-not
+.. _issue 137: https://bitbucket.org/ned/coveragepy/issue/137/provide-docs-with-source-distribution
+.. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests
+.. _issue 143: https://bitbucket.org/ned/coveragepy/issue/143/omit-doesnt-seem-to-work-in-coverage
+.. _issue 153: https://bitbucket.org/ned/coveragepy/issue/153/non-existent-filename-triggers
+.. _issue 156: https://bitbucket.org/ned/coveragepy/issue/156/a-completely-unexecuted-file-shows-14
+.. _issue 163: https://bitbucket.org/ned/coveragepy/issue/163/problem-with-include-and-omit-filename
+.. _issue 171: https://bitbucket.org/ned/coveragepy/issue/171/how-to-contribute-and-run-tests
+.. _issue 193: https://bitbucket.org/ned/coveragepy/issue/193/unicodedecodeerror-on-htmlpy
+.. _issue 201: https://bitbucket.org/ned/coveragepy/issue/201/coverage-using-django-14-with-pydb-on
+.. _issue 202: https://bitbucket.org/ned/coveragepy/issue/202/get-rid-of-ez_setuppy-and
+.. _issue 203: https://bitbucket.org/ned/coveragepy/issue/203/duplicate-filenames-reported-when-filename
+.. _issue 205: https://bitbucket.org/ned/coveragepy/issue/205/make-pydoc-coverage-more-friendly
+.. _issue 206: https://bitbucket.org/ned/coveragepy/issue/206/pydoc-coveragecoverage-fails-with-an-error
+.. _issue 210: https://bitbucket.org/ned/coveragepy/issue/210/if-theres-no-coverage-data-coverage-xml
+.. _issue 214: https://bitbucket.org/ned/coveragepy/issue/214/coveragepy-measures-itself-on-precise
+
+
+Version 3.5.3 --- 29 September 2012
+-----------------------------------
+
+- Line numbers in the HTML report line up better with the source lines, fixing
+  `issue 197`_, thanks Marius Gedminas.
+
+- When specifying a directory as the source= option, the directory itself no
+  longer needs to have a ``__init__.py`` file, though its subdirectories do, to
+  be considered as source files.
+
+- Files encoded as UTF-8 with a BOM are now properly handled, fixing
+  `issue 179`_.  Thanks, Pablo Carballo.
+
+- Fixed more cases of non-Python files being reported as Python source, and
+  then not being able to parse them as Python.  Closes `issue 82`_ (again).
+  Thanks, Julian Berman.
+
+- Fixed memory leaks under Python 3, thanks, Brett Cannon. Closes `issue 147`_.
+
+- Optimized .pyo files may not have been handled correctly, `issue 195`_.
+  Thanks, Marius Gedminas.
+
+- Certain unusually named file paths could have been mangled during reporting,
+  `issue 194`_.  Thanks, Marius Gedminas.
+
+- Try to do a better job of the impossible task of detecting when we can't
+  build the C extension, fixing `issue 183`_.
+
+- Testing is now done with `tox`_, thanks, Marc Abramowitz.
+
+.. _issue 82: https://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report
+.. _issue 147: https://bitbucket.org/ned/coveragepy/issue/147/massive-memory-usage-by-ctracer
+.. _issue 179: https://bitbucket.org/ned/coveragepy/issue/179/htmlreporter-fails-when-source-file-is
+.. _issue 183: https://bitbucket.org/ned/coveragepy/issue/183/install-fails-for-python-23
+.. _issue 194: https://bitbucket.org/ned/coveragepy/issue/194/filelocatorrelative_filename-could-mangle
+.. _issue 195: https://bitbucket.org/ned/coveragepy/issue/195/pyo-file-handling-in-codeunit
+.. _issue 197: https://bitbucket.org/ned/coveragepy/issue/197/line-numbers-in-html-report-do-not-align
+.. _tox: http://tox.readthedocs.org/
+
+
+
+Version 3.5.2 --- 4 May 2012
+----------------------------
+
+No changes since 3.5.2.b1
+
+
+Version 3.5.2b1 --- 29 April 2012
+---------------------------------
+
+- The HTML report has slightly tweaked controls: the buttons at the top of
+  the page are color-coded to the source lines they affect.
+
+- Custom CSS can be applied to the HTML report by specifying a CSS file as
+  the extra_css configuration value in the [html] section.
+
+- Source files with custom encodings declared in a comment at the top are now
+  properly handled during reporting on Python 2.  Python 3 always handled them
+  properly.  This fixes `issue 157`_.
+
+- Backup files left behind by editors are no longer collected by the source=
+  option, fixing `issue 168`_.
+
+- If a file doesn't parse properly as Python, we don't report it as an error
+  if the filename seems like maybe it wasn't meant to be Python.  This is a
+  pragmatic fix for `issue 82`_.
+
+- The ``-m`` switch on ``coverage report``, which includes missing line numbers
+  in the summary report, can now be specifed as ``show_missing`` in the
+  config file.  Closes `issue 173`_.
+
+- When running a module with ``coverage run -m <modulename>``, certain details
+  of the execution environment weren't the same as for
+  ``python -m <modulename>``.  This had the unfortunate side-effect of making
+  ``coverage run -m unittest discover`` not work if you had tests in a
+  directory named "test".  This fixes `issue 155`_.
+
+- Now the exit status of your product code is properly used as the process
+  status when running ``python -m coverage run ...``.  Thanks, JT Olds.
+
+- When installing into pypy, we no longer attempt (and fail) to compile
+  the C tracer function, closing `issue 166`_.
+
+.. _issue 82: https://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report
+.. _issue 155: https://bitbucket.org/ned/coveragepy/issue/155/cant-use-coverage-run-m-unittest-discover
+.. _issue 157: https://bitbucket.org/ned/coveragepy/issue/157/chokes-on-source-files-with-non-utf-8
+.. _issue 166: https://bitbucket.org/ned/coveragepy/issue/166/dont-try-to-compile-c-extension-on-pypy
+.. _issue 168: https://bitbucket.org/ned/coveragepy/issue/168/dont-be-alarmed-by-emacs-droppings
+.. _issue 173: https://bitbucket.org/ned/coveragepy/issue/173/theres-no-way-to-specify-show-missing-in
+
+
+Version 3.5.1 --- 23 September 2011
+-----------------------------------
+
+- The ``[paths]`` feature unfortunately didn't work in real world situations
+  where you wanted to, you know, report on the combined data.  Now all paths
+  stored in the combined file are canonicalized properly.
+
+
+Version 3.5.1b1 --- 28 August 2011
+----------------------------------
+
+- When combining data files from parallel runs, you can now instruct coverage
+  about which directories are equivalent on different machines.  A ``[paths]``
+  section in the configuration file lists paths that are to be considered
+  equivalent.  Finishes `issue 17`_.
+
+- for-else constructs are understood better, and don't cause erroneous partial
+  branch warnings.  Fixes `issue 122`_.
+
+- Branch coverage for ``with`` statements is improved, fixing `issue 128`_.
+
+- The number of partial branches reported on the HTML summary page was
+  different than the number reported on the individual file pages.  This is
+  now fixed.
+
+- An explicit include directive to measure files in the Python installation
+  wouldn't work because of the standard library exclusion.  Now the include
+  directive takes precendence, and the files will be measured.  Fixes
+  `issue 138`_.
+
+- The HTML report now handles Unicode characters in Python source files
+  properly.  This fixes `issue 124`_ and `issue 144`_. Thanks, Devin
+  Jeanpierre.
+
+- In order to help the core developers measure the test coverage of the
+  standard library, Brandon Rhodes devised an aggressive hack to trick Python
+  into running some coverage code before anything else in the process.
+  See the coverage/fullcoverage directory if you are interested.
+
+.. _issue 17: http://bitbucket.org/ned/coveragepy/issue/17/support-combining-coverage-data-from
+.. _issue 122: http://bitbucket.org/ned/coveragepy/issue/122/for-else-always-reports-missing-branch
+.. _issue 124: http://bitbucket.org/ned/coveragepy/issue/124/no-arbitrary-unicode-in-html-reports-in
+.. _issue 128: http://bitbucket.org/ned/coveragepy/issue/128/branch-coverage-of-with-statement-in-27
+.. _issue 138: http://bitbucket.org/ned/coveragepy/issue/138/include-should-take-precedence-over-is
+.. _issue 144: http://bitbucket.org/ned/coveragepy/issue/144/failure-generating-html-output-for
+
+
+Version 3.5 --- 29 June 2011
+----------------------------
+
+- The HTML report hotkeys now behave slightly differently when the current
+  chunk isn't visible at all:  a chunk on the screen will be selected,
+  instead of the old behavior of jumping to the literal next chunk.
+  The hotkeys now work in Google Chrome.  Thanks, Guido van Rossum.
+
+
+Version 3.5b1 --- 5 June 2011
+-----------------------------
+
+- The HTML report now has hotkeys.  Try ``n``, ``s``, ``m``, ``x``, ``b``,
+  ``p``, and ``c`` on the overview page to change the column sorting.
+  On a file page, ``r``, ``m``, ``x``, and ``p`` toggle the run, missing,
+  excluded, and partial line markings.  You can navigate the highlighted
+  sections of code by using the ``j`` and ``k`` keys for next and previous.
+  The ``1`` (one) key jumps to the first highlighted section in the file,
+  and ``0`` (zero) scrolls to the top of the file.
+
+- The ``--omit`` and ``--include`` switches now interpret their values more
+  usefully.  If the value starts with a wildcard character, it is used as-is.
+  If it does not, it is interpreted relative to the current directory.
+  Closes `issue 121`_.
+
+- Partial branch warnings can now be pragma'd away.  The configuration option
+  ``partial_branches`` is a list of regular expressions.  Lines matching any of
+  those expressions will never be marked as a partial branch.  In addition,
+  there's a built-in list of regular expressions marking statements which should
+  never be marked as partial.  This list includes ``while True:``, ``while 1:``,
+  ``if 1:``, and ``if 0:``.
+
+- The ``coverage()`` constructor accepts single strings for the ``omit=`` and
+  ``include=`` arguments, adapting to a common error in programmatic use.
+
+- Modules can now be run directly using ``coverage run -m modulename``, to
+  mirror Python's ``-m`` flag.  Closes `issue 95`_, thanks, Brandon Rhodes.
+
+- ``coverage run`` didn't emulate Python accurately in one small detail: the
+  current directory inserted into ``sys.path`` was relative rather than
+  absolute. This is now fixed.
+
+- HTML reporting is now incremental: a record is kept of the data that
+  produced the HTML reports, and only files whose data has changed will
+  be generated.  This should make most HTML reporting faster.
+
+- Pathological code execution could disable the trace function behind our
+  backs, leading to incorrect code measurement.  Now if this happens,
+  coverage.py will issue a warning, at least alerting you to the problem.
+  Closes `issue 93`_.  Thanks to Marius Gedminas for the idea.
+
+- The C-based trace function now behaves properly when saved and restored
+  with ``sys.gettrace()`` and ``sys.settrace()``.  This fixes `issue 125`_
+  and `issue 123`_.  Thanks, Devin Jeanpierre.
+
+- Source files are now opened with Python 3.2's ``tokenize.open()`` where
+  possible, to get the best handling of Python source files with encodings.
+  Closes `issue 107`_, thanks, Brett Cannon.
+
+- Syntax errors in supposed Python files can now be ignored during reporting
+  with the ``-i`` switch just like other source errors.  Closes `issue 115`_.
+
+- Installation from source now succeeds on machines without a C compiler,
+  closing `issue 80`_.
+
+- Coverage.py can now be run directly from a working tree by specifying
+  the directory name to python:  ``python coverage_py_working_dir run ...``.
+  Thanks, Brett Cannon.
+
+- A little bit of Jython support: `coverage run` can now measure Jython
+  execution by adapting when $py.class files are traced. Thanks, Adi Roiban.
+  Jython still doesn't provide the Python libraries needed to make
+  coverage reporting work, unfortunately.
+
+- Internally, files are now closed explicitly, fixing `issue 104`_.  Thanks,
+  Brett Cannon.
+
+.. _issue 80: https://bitbucket.org/ned/coveragepy/issue/80/is-there-a-duck-typing-way-to-know-we-cant
+.. _issue 93: http://bitbucket.org/ned/coveragepy/issue/93/copying-a-mock-object-breaks-coverage
+.. _issue 95: https://bitbucket.org/ned/coveragepy/issue/95/run-subcommand-should-take-a-module-name
+.. _issue 104: https://bitbucket.org/ned/coveragepy/issue/104/explicitly-close-files
+.. _issue 107: https://bitbucket.org/ned/coveragepy/issue/107/codeparser-not-opening-source-files-with
+.. _issue 115: https://bitbucket.org/ned/coveragepy/issue/115/fail-gracefully-when-reporting-on-file
+.. _issue 121: https://bitbucket.org/ned/coveragepy/issue/121/filename-patterns-are-applied-stupidly
+.. _issue 123: https://bitbucket.org/ned/coveragepy/issue/123/pyeval_settrace-used-in-way-that-breaks
+.. _issue 125: https://bitbucket.org/ned/coveragepy/issue/125/coverage-removes-decoratortoolss-tracing
+
+
+Version 3.4 --- 19 September 2010
+---------------------------------
+
+- The XML report is now sorted by package name, fixing `issue 88`_.
+
+- Programs that exited with ``sys.exit()`` with no argument weren't handled
+  properly, producing a coverage.py stack trace.  That is now fixed.
+
+.. _issue 88: http://bitbucket.org/ned/coveragepy/issue/88/xml-report-lists-packages-in-random-order
+
+
+Version 3.4b2 --- 6 September 2010
+----------------------------------
+
+- Completely unexecuted files can now be included in coverage results, reported
+  as 0% covered.  This only happens if the --source option is specified, since
+  coverage.py needs guidance about where to look for source files.
+
+- The XML report output now properly includes a percentage for branch coverage,
+  fixing `issue 65`_ and `issue 81`_.
+
+- Coverage percentages are now displayed uniformly across reporting methods.
+  Previously, different reports could round percentages differently.  Also,
+  percentages are only reported as 0% or 100% if they are truly 0 or 100, and
+  are rounded otherwise.  Fixes `issue 41`_ and `issue 70`_.
+
+- The precision of reported coverage percentages can be set with the
+  ``[report] precision`` config file setting.  Completes `issue 16`_.
+
+- Threads derived from ``threading.Thread`` with an overridden `run` method
+  would report no coverage for the `run` method.  This is now fixed, closing
+  `issue 85`_.
+
+.. _issue 16: http://bitbucket.org/ned/coveragepy/issue/16/allow-configuration-of-accuracy-of-percentage-totals
+.. _issue 41: http://bitbucket.org/ned/coveragepy/issue/41/report-says-100-when-it-isnt-quite-there
+.. _issue 65: http://bitbucket.org/ned/coveragepy/issue/65/branch-option-not-reported-in-cobertura
+.. _issue 70: http://bitbucket.org/ned/coveragepy/issue/70/text-report-and-html-report-disagree-on-coverage
+.. _issue 81: http://bitbucket.org/ned/coveragepy/issue/81/xml-report-does-not-have-condition-coverage-attribute-for-lines-with-a
+.. _issue 85: http://bitbucket.org/ned/coveragepy/issue/85/threadrun-isnt-measured
+
+
+Version 3.4b1 --- 21 August 2010
+--------------------------------
+
+- BACKWARD INCOMPATIBILITY: the ``--omit`` and ``--include`` switches now take
+  file patterns rather than file prefixes, closing `issue 34`_ and `issue 36`_.
+
+- BACKWARD INCOMPATIBILITY: the `omit_prefixes` argument is gone throughout
+  coverage.py, replaced with `omit`, a list of filename patterns suitable for
+  `fnmatch`.  A parallel argument `include` controls what files are included.
+
+- The run command now has a ``--source`` switch, a list of directories or
+  module names.  If provided, coverage.py will only measure execution in those
+  source files.
+
+- Various warnings are printed to stderr for problems encountered during data
+  measurement: if a ``--source`` module has no Python source to measure, or is
+  never encountered at all, or if no data is collected.
+
+- The reporting commands (report, annotate, html, and xml) now have an
+  ``--include`` switch to restrict reporting to modules matching those file
+  patterns, similar to the existing ``--omit`` switch. Thanks, Zooko.
+
+- The run command now supports ``--include`` and ``--omit`` to control what
+  modules it measures. This can speed execution and reduce the amount of data
+  during reporting. Thanks Zooko.
+
+- Since coverage.py 3.1, using the Python trace function has been slower than
+  it needs to be.  A cache of tracing decisions was broken, but has now been
+  fixed.
+
+- Python 2.7 and 3.2 have introduced new opcodes that are now supported.
+
+- Python files with no statements, for example, empty ``__init__.py`` files,
+  are now reported as having zero statements instead of one.  Fixes `issue 1`_.
+
+- Reports now have a column of missed line counts rather than executed line
+  counts, since developers should focus on reducing the missed lines to zero,
+  rather than increasing the executed lines to varying targets.  Once
+  suggested, this seemed blindingly obvious.
+
+- Line numbers in HTML source pages are clickable, linking directly to that
+  line, which is highlighted on arrival.  Added a link back to the index page
+  at the bottom of each HTML page.
+
+- Programs that call ``os.fork`` will properly collect data from both the child
+  and parent processes.  Use ``coverage run -p`` to get two data files that can
+  be combined with ``coverage combine``.  Fixes `issue 56`_.
+
+- Coverage is now runnable as a module: ``python -m coverage``.  Thanks,
+  Brett Cannon.
+
+- When measuring code running in a virtualenv, most of the system library was
+  being measured when it shouldn't have been.  This is now fixed.
+
+- Doctest text files are no longer recorded in the coverage data, since they
+  can't be reported anyway.  Fixes `issue 52`_ and `issue 61`_.
+
+- Jinja HTML templates compile into Python code using the HTML filename,
+  which confused coverage.py.  Now these files are no longer traced, fixing
+  `issue 82`_.
+
+- Source files can have more than one dot in them (foo.test.py), and will be
+  treated properly while reporting.  Fixes `issue 46`_.
+
+- Source files with DOS line endings are now properly tokenized for syntax
+  coloring on non-DOS machines.  Fixes `issue 53`_.
+
+- Unusual code structure that confused exits from methods with exits from
+  classes is now properly analyzed.  See `issue 62`_.
+
+- Asking for an HTML report with no files now shows a nice error message rather
+  than a cryptic failure ('int' object is unsubscriptable). Fixes `issue 59`_.
+
+.. _issue 1:  http://bitbucket.org/ned/coveragepy/issue/1/empty-__init__py-files-are-reported-as-1-executable
+.. _issue 34: http://bitbucket.org/ned/coveragepy/issue/34/enhanced-omit-globbing-handling
+.. _issue 36: http://bitbucket.org/ned/coveragepy/issue/36/provide-regex-style-omit
+.. _issue 46: http://bitbucket.org/ned/coveragepy/issue/46
+.. _issue 53: http://bitbucket.org/ned/coveragepy/issue/53
+.. _issue 52: http://bitbucket.org/ned/coveragepy/issue/52/doctesttestfile-confuses-source-detection
+.. _issue 56: http://bitbucket.org/ned/coveragepy/issue/56
+.. _issue 61: http://bitbucket.org/ned/coveragepy/issue/61/annotate-i-doesnt-work
+.. _issue 62: http://bitbucket.org/ned/coveragepy/issue/62
+.. _issue 59: http://bitbucket.org/ned/coveragepy/issue/59/html-report-fails-with-int-object-is
+.. _issue 82: http://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report
+
+
+Version 3.3.1 --- 6 March 2010
+------------------------------
+
+- Using `parallel=True` in .coveragerc file prevented reporting, but now does
+  not, fixing `issue 49`_.
+
+- When running your code with "coverage run", if you call `sys.exit()`,
+  coverage.py will exit with that status code, fixing `issue 50`_.
+
+.. _issue 49: http://bitbucket.org/ned/coveragepy/issue/49
+.. _issue 50: http://bitbucket.org/ned/coveragepy/issue/50
+
+
+Version 3.3 --- 24 February 2010
+--------------------------------
+
+- Settings are now read from a .coveragerc file.  A specific file can be
+  specified on the command line with --rcfile=FILE.  The name of the file can
+  be programmatically set with the `config_file` argument to the coverage()
+  constructor, or reading a config file can be disabled with
+  `config_file=False`.
+
+- Fixed a problem with nested loops having their branch possibilities
+  mischaracterized: `issue 39`_.
+
+- Added coverage.process_start to enable coverage measurement when Python
+  starts.
+
+- Parallel data file names now have a random number appended to them in
+  addition to the machine name and process id.
+
+- Parallel data files combined with "coverage combine" are deleted after
+  they're combined, to clean up unneeded files.  Fixes `issue 40`_.
+
+- Exceptions thrown from product code run with "coverage run" are now displayed
+  without internal coverage.py frames, so the output is the same as when the
+  code is run without coverage.py.
+
+- The `data_suffix` argument to the coverage constructor is now appended with
+  an added dot rather than simply appended, so that .coveragerc files will not
+  be confused for data files.
+
+- Python source files that don't end with a newline can now be executed, fixing
+  `issue 47`_.
+
+- Added an AUTHORS.txt file.
+
+.. _issue 39: http://bitbucket.org/ned/coveragepy/issue/39
+.. _issue 40: http://bitbucket.org/ned/coveragepy/issue/40
+.. _issue 47: http://bitbucket.org/ned/coveragepy/issue/47
+
+
+Version 3.2 --- 5 December 2009
+-------------------------------
+
+- Added a ``--version`` option on the command line.
+
+
+Version 3.2b4 --- 1 December 2009
+---------------------------------
+
+- Branch coverage improvements:
+
+  - The XML report now includes branch information.
+
+- Click-to-sort HTML report columns are now persisted in a cookie.  Viewing
+  a report will sort it first the way you last had a coverage report sorted.
+  Thanks, `Chris Adams`_.
+
+- On Python 3.x, setuptools has been replaced by `Distribute`_.
+
+.. _Distribute: http://packages.python.org/distribute/
+
+
+Version 3.2b3 --- 23 November 2009
+----------------------------------
+
+- Fixed a memory leak in the C tracer that was introduced in 3.2b1.
+
+- Branch coverage improvements:
+
+  - Branches to excluded code are ignored.
+
+- The table of contents in the HTML report is now sortable: click the headers
+  on any column.  Thanks, `Chris Adams`_.
+
+.. _Chris Adams: http://improbable.org/chris/
+
+
+Version 3.2b2 --- 19 November 2009
+----------------------------------
+
+- Branch coverage improvements:
+
+  - Classes are no longer incorrectly marked as branches: `issue 32`_.
+
+  - "except" clauses with types are no longer incorrectly marked as branches:
+    `issue 35`_.
+
+- Fixed some problems syntax coloring sources with line continuations and
+  source with tabs: `issue 30`_ and `issue 31`_.
+
+- The --omit option now works much better than before, fixing `issue 14`_ and
+  `issue 33`_.  Thanks, Danek Duvall.
+
+.. _issue 14: http://bitbucket.org/ned/coveragepy/issue/14
+.. _issue 30: http://bitbucket.org/ned/coveragepy/issue/30
+.. _issue 31: http://bitbucket.org/ned/coveragepy/issue/31
+.. _issue 32: http://bitbucket.org/ned/coveragepy/issue/32
+.. _issue 33: http://bitbucket.org/ned/coveragepy/issue/33
+.. _issue 35: http://bitbucket.org/ned/coveragepy/issue/35
+
+
+Version 3.2b1 --- 10 November 2009
+----------------------------------
+
+- Branch coverage!
+
+- XML reporting has file paths that let Cobertura find the source code.
+
+- The tracer code has changed, it's a few percent faster.
+
+- Some exceptions reported by the command line interface have been cleaned up
+  so that tracebacks inside coverage.py aren't shown.  Fixes `issue 23`_.
+
+.. _issue 23: http://bitbucket.org/ned/coveragepy/issue/23
+
+
+Version 3.1 --- 4 October 2009
+------------------------------
+
+- Source code can now be read from eggs.  Thanks, Ross Lawley.  Fixes
+  `issue 25`_.
+
+.. _issue 25: http://bitbucket.org/ned/coveragepy/issue/25
+
+
+Version 3.1b1 --- 27 September 2009
+-----------------------------------
+
+- Python 3.1 is now supported.
+
+- Coverage.py has a new command line syntax with sub-commands.  This expands
+  the possibilities for adding features and options in the future.  The old
+  syntax is still supported.  Try "coverage help" to see the new commands.
+  Thanks to Ben Finney for early help.
+
+- Added an experimental "coverage xml" command for producing coverage reports
+  in a Cobertura-compatible XML format.  Thanks, Bill Hart.
+
+- Added the --timid option to enable a simpler slower trace function that works
+  for DecoratorTools projects, including TurboGears.  Fixed `issue 12`_ and
+  `issue 13`_.
+
+- HTML reports show modules from other directories.  Fixed `issue 11`_.
+
+- HTML reports now display syntax-colored Python source.
+
+- Programs that change directory will still write .coverage files in the
+  directory where execution started.  Fixed `issue 24`_.
+
+- Added a "coverage debug" command for getting diagnostic information about the
+  coverage.py installation.
+
+.. _issue 11: http://bitbucket.org/ned/coveragepy/issue/11
+.. _issue 12: http://bitbucket.org/ned/coveragepy/issue/12
+.. _issue 13: http://bitbucket.org/ned/coveragepy/issue/13
+.. _issue 24: http://bitbucket.org/ned/coveragepy/issue/24
+
+
+Version 3.0.1 --- 7 July 2009
+-----------------------------
+
+- Removed the recursion limit in the tracer function.  Previously, code that
+  ran more than 500 frames deep would crash. Fixed `issue 9`_.
+
+- Fixed a bizarre problem involving pyexpat, whereby lines following XML parser
+  invocations could be overlooked.  Fixed `issue 10`_.
+
+- On Python 2.3, coverage.py could mis-measure code with exceptions being
+  raised.  This is now fixed.
+
+- The coverage.py code itself will now not be measured by coverage.py, and no
+  coverage modules will be mentioned in the nose --with-cover plug-in.  Fixed
+  `issue 8`_.
+
+- When running source files, coverage.py now opens them in universal newline
+  mode just like Python does.  This lets it run Windows files on Mac, for
+  example.
+
+.. _issue 9: http://bitbucket.org/ned/coveragepy/issue/9
+.. _issue 10: http://bitbucket.org/ned/coveragepy/issue/10
+.. _issue 8: http://bitbucket.org/ned/coveragepy/issue/8
+
+
+Version 3.0 --- 13 June 2009
+----------------------------
+
+- Fixed the way the Python library was ignored.  Too much code was being
+  excluded the old way.
+
+- Tabs are now properly converted in HTML reports.  Previously indentation was
+  lost.  Fixed `issue 6`_.
+
+- Nested modules now get a proper flat_rootname.  Thanks, Christian Heimes.
+
+.. _issue 6: http://bitbucket.org/ned/coveragepy/issue/6
+
+
+Version 3.0b3 --- 16 May 2009
+-----------------------------
+
+- Added parameters to coverage.__init__ for options that had been set on the
+  coverage object itself.
+
+- Added clear_exclude() and get_exclude_list() methods for programmatic
+  manipulation of the exclude regexes.
+
+- Added coverage.load() to read previously-saved data from the data file.
+
+- Improved the finding of code files.  For example, .pyc files that have been
+  installed after compiling are now located correctly.  Thanks, Detlev
+  Offenbach.
+
+- When using the object API (that is, constructing a coverage() object), data
+  is no longer saved automatically on process exit.  You can re-enable it with
+  the auto_data=True parameter on the coverage() constructor. The module-level
+  interface still uses automatic saving.
+
+
+Version 3.0b --- 30 April 2009
+------------------------------
+
+HTML reporting, and continued refactoring.
+
+- HTML reports and annotation of source files: use the new -b (browser) switch.
+  Thanks to George Song for code, inspiration and guidance.
+
+- Code in the Python standard library is not measured by default.  If you need
+  to measure standard library code, use the -L command-line switch during
+  execution, or the cover_pylib=True argument to the coverage() constructor.
+
+- Source annotation into a directory (-a -d) behaves differently.  The
+  annotated files are named with their hierarchy flattened so that same-named
+  files from different directories no longer collide.  Also, only files in the
+  current tree are included.
+
+- coverage.annotate_file is no longer available.
+
+- Programs executed with -x now behave more as they should, for example,
+  __file__ has the correct value.
+
+- .coverage data files have a new pickle-based format designed for better
+  extensibility.
+
+- Removed the undocumented cache_file argument to coverage.usecache().
+
+
+Version 3.0b1 --- 7 March 2009
+------------------------------
+
+Major overhaul.
+
+- Coverage is now a package rather than a module.  Functionality has been split
+  into classes.
+
+- The trace function is implemented in C for speed.  Coverage runs are now
+  much faster.  Thanks to David Christian for productive micro-sprints and
+  other encouragement.
+
+- Executable lines are identified by reading the line number tables in the
+  compiled code, removing a great deal of complicated analysis code.
+
+- Precisely which lines are considered executable has changed in some cases.
+  Therefore, your coverage stats may also change slightly.
+
+- The singleton coverage object is only created if the module-level functions
+  are used.  This maintains the old interface while allowing better
+  programmatic use of Coverage.
+
+- The minimum supported Python version is 2.3.
+
+
+Version 2.85 --- 14 September 2008
+----------------------------------
+
+- Add support for finding source files in eggs. Don't check for
+  morf's being instances of ModuleType, instead use duck typing so that
+  pseudo-modules can participate. Thanks, Imri Goldberg.
+
+- Use os.realpath as part of the fixing of file names so that symlinks won't
+  confuse things. Thanks, Patrick Mezard.
+
+
+Version 2.80 --- 25 May 2008
+----------------------------
+
+- Open files in rU mode to avoid line ending craziness. Thanks, Edward Loper.
+
+
+Version 2.78 --- 30 September 2007
+----------------------------------
+
+- Don't try to predict whether a file is Python source based on the extension.
+  Extension-less files are often Pythons scripts. Instead, simply parse the file
+  and catch the syntax errors. Hat tip to Ben Finney.
+
+
+Version 2.77 --- 29 July 2007
+-----------------------------
+
+- Better packaging.
+
+
+Version 2.76 --- 23 July 2007
+-----------------------------
+
+- Now Python 2.5 is *really* fully supported: the body of the new with
+  statement is counted as executable.
+
+
+Version 2.75 --- 22 July 2007
+-----------------------------
+
+- Python 2.5 now fully supported. The method of dealing with multi-line
+  statements is now less sensitive to the exact line that Python reports during
+  execution. Pass statements are handled specially so that their disappearance
+  during execution won't throw off the measurement.
+
+
+Version 2.7 --- 21 July 2007
+----------------------------
+
+- "#pragma: nocover" is excluded by default.
+
+- Properly ignore docstrings and other constant expressions that appear in the
+  middle of a function, a problem reported by Tim Leslie.
+
+- coverage.erase() shouldn't clobber the exclude regex. Change how parallel
+  mode is invoked, and fix erase() so that it erases the cache when called
+  programmatically.
+
+- In reports, ignore code executed from strings, since we can't do anything
+  useful with it anyway.
+
+- Better file handling on Linux, thanks Guillaume Chazarain.
+
+- Better shell support on Windows, thanks Noel O'Boyle.
+
+- Python 2.2 support maintained, thanks Catherine Proulx.
+
+- Minor changes to avoid lint warnings.
+
+
+Version 2.6 --- 23 August 2006
+------------------------------
+
+- Applied Joseph Tate's patch for function decorators.
+
+- Applied Sigve Tjora and Mark van der Wal's fixes for argument handling.
+
+- Applied Geoff Bache's parallel mode patch.
+
+- Refactorings to improve testability. Fixes to command-line logic for parallel
+  mode and collect.
+
+
+Version 2.5 --- 4 December 2005
+-------------------------------
+
+- Call threading.settrace so that all threads are measured. Thanks Martin
+  Fuzzey.
+
+- Add a file argument to report so that reports can be captured to a different
+  destination.
+
+- coverage.py can now measure itself.
+
+- Adapted Greg Rogers' patch for using relative file names, and sorting and
+  omitting files to report on.
+
+
+Version 2.2 --- 31 December 2004
+--------------------------------
+
+- Allow for keyword arguments in the module global functions. Thanks, Allen.
+
+
+Version 2.1 --- 14 December 2004
+--------------------------------
+
+- Return 'analysis' to its original behavior and add 'analysis2'. Add a global
+  for 'annotate', and factor it, adding 'annotate_file'.
+
+
+Version 2.0 --- 12 December 2004
+--------------------------------
+
+Significant code changes.
+
+- Finding executable statements has been rewritten so that docstrings and
+  other quirks of Python execution aren't mistakenly identified as missing
+  lines.
+
+- Lines can be excluded from consideration, even entire suites of lines.
+
+- The file system cache of covered lines can be disabled programmatically.
+
+- Modernized the code.
+
+
+Earlier History
+---------------
+
+2001-12-04 GDR Created.
+
+2001-12-06 GDR Added command-line interface and source code annotation.
+
+2001-12-09 GDR Moved design and interface to separate documents.
+
+2001-12-10 GDR Open cache file as binary on Windows. Allow simultaneous -e and
+-x, or -a and -r.
+
+2001-12-12 GDR Added command-line help. Cache analysis so that it only needs to
+be done once when you specify -a and -r.
+
+2001-12-13 GDR Improved speed while recording. Portable between Python 1.5.2
+and 2.1.1.
+
+2002-01-03 GDR Module-level functions work correctly.
+
+2002-01-07 GDR Update sys.path when running a file with the -x option, so that
+it matches the value the program would get if it were run on its own.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..0150d90
--- /dev/null
@@ -0,0 +1,16 @@
+# MANIFEST.in file for coverage.py
+recursive-include coverage/htmlfiles *
+recursive-include coverage/fullcoverage *
+
+include coverage.egg-info/*.*
+include setup.py
+include README.txt
+include CHANGES.txt
+include AUTHORS.txt
+include requirements.txt
+include igor.py
+include tox.ini
+
+recursive-include test *
+recursive-include doc *.rst
+global-exclude *.pyc
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..0bfedd5
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,38 @@
+Metadata-Version: 1.1\r
+Name: coverage\r
+Version: 3.6\r
+Summary: Code coverage measurement for Python\r
+Home-page: http://nedbatchelder.com/code/coverage\r
+Author: Ned Batchelder and others\r
+Author-email: ned@nedbatchelder.com\r
+License: BSD\r
+Description: Coverage.py measures code coverage, typically during test execution. It uses\r
+        the code analysis tools and tracing hooks provided in the Python standard\r
+        library to determine which lines are executable, and which have been executed.\r
+        \r
+        Coverage.py runs on Pythons 2.3 through 3.3, and PyPy 1.9.\r
+        \r
+        Documentation is at `nedbatchelder.com <http://nedbatchelder.com/code/coverage>`_.  Code repository and issue\r
+        tracker are at `bitbucket.org <http://bitbucket.org/ned/coveragepy>`_.\r
+        \r
+        New in 3.6: ``--fail-under``, and >20 bugs closed.\r
+        \r
+        New in 3.5: Branch coverage exclusions, keyboard shortcuts in HTML report.\r
+        \r
+        New in 3.4: Better control over source to measure, and unexecuted files\r
+        can be reported.\r
+        \r
+        New in 3.3: .coveragerc files.\r
+        \r
+        New in 3.2: Branch coverage!\r
+Keywords: code coverage testing\r
+Platform: UNKNOWN\r
+Classifier: Environment :: Console\r
+Classifier: Intended Audience :: Developers\r
+Classifier: License :: OSI Approved :: BSD License\r
+Classifier: Operating System :: OS Independent\r
+Classifier: Programming Language :: Python :: 2\r
+Classifier: Programming Language :: Python :: 3\r
+Classifier: Topic :: Software Development :: Quality Assurance\r
+Classifier: Topic :: Software Development :: Testing\r
+Classifier: Development Status :: 5 - Production/Stable\r
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..2c6fe3d
--- /dev/null
@@ -0,0 +1,9 @@
+Coverage.py: code coverage testing for Python
+
+Coverage.py measures code coverage, typically during test execution.  It uses
+the code analysis tools and tracing hooks provided in the Python standard
+library to determine which lines are executable, and which have been executed.
+
+For more information, see http://nedbatchelder.com/code/coverage
+
+Code repo and issue tracking are at http://bitbucket.org/ned/coveragepy
diff --git a/coverage.egg-info/PKG-INFO b/coverage.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..0bfedd5
--- /dev/null
@@ -0,0 +1,38 @@
+Metadata-Version: 1.1\r
+Name: coverage\r
+Version: 3.6\r
+Summary: Code coverage measurement for Python\r
+Home-page: http://nedbatchelder.com/code/coverage\r
+Author: Ned Batchelder and others\r
+Author-email: ned@nedbatchelder.com\r
+License: BSD\r
+Description: Coverage.py measures code coverage, typically during test execution. It uses\r
+        the code analysis tools and tracing hooks provided in the Python standard\r
+        library to determine which lines are executable, and which have been executed.\r
+        \r
+        Coverage.py runs on Pythons 2.3 through 3.3, and PyPy 1.9.\r
+        \r
+        Documentation is at `nedbatchelder.com <http://nedbatchelder.com/code/coverage>`_.  Code repository and issue\r
+        tracker are at `bitbucket.org <http://bitbucket.org/ned/coveragepy>`_.\r
+        \r
+        New in 3.6: ``--fail-under``, and >20 bugs closed.\r
+        \r
+        New in 3.5: Branch coverage exclusions, keyboard shortcuts in HTML report.\r
+        \r
+        New in 3.4: Better control over source to measure, and unexecuted files\r
+        can be reported.\r
+        \r
+        New in 3.3: .coveragerc files.\r
+        \r
+        New in 3.2: Branch coverage!\r
+Keywords: code coverage testing\r
+Platform: UNKNOWN\r
+Classifier: Environment :: Console\r
+Classifier: Intended Audience :: Developers\r
+Classifier: License :: OSI Approved :: BSD License\r
+Classifier: Operating System :: OS Independent\r
+Classifier: Programming Language :: Python :: 2\r
+Classifier: Programming Language :: Python :: 3\r
+Classifier: Topic :: Software Development :: Quality Assurance\r
+Classifier: Topic :: Software Development :: Testing\r
+Classifier: Development Status :: 5 - Production/Stable\r
diff --git a/coverage.egg-info/SOURCES.txt b/coverage.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..9810573
--- /dev/null
@@ -0,0 +1,237 @@
+AUTHORS.txt
+CHANGES.txt
+MANIFEST.in
+README.txt
+igor.py
+requirements.txt
+setup.py
+test_old.sh
+tox.ini
+coverage/__init__.py
+coverage/__main__.py
+coverage/annotate.py
+coverage/backward.py
+coverage/bytecode.py
+coverage/cmdline.py
+coverage/codeunit.py
+coverage/collector.py
+coverage/config.py
+coverage/control.py
+coverage/data.py
+coverage/execfile.py
+coverage/files.py
+coverage/html.py
+coverage/misc.py
+coverage/parser.py
+coverage/phystokens.py
+coverage/report.py
+coverage/results.py
+coverage/summary.py
+coverage/templite.py
+coverage/tracer.c
+coverage/version.py
+coverage/xmlreport.py
+coverage.egg-info/PKG-INFO
+coverage.egg-info/SOURCES.txt
+coverage.egg-info/dependency_links.txt
+coverage.egg-info/entry_points.txt
+coverage.egg-info/not-zip-safe
+coverage.egg-info/top_level.txt
+coverage/fullcoverage/encodings.py
+coverage/htmlfiles/coverage_html.js
+coverage/htmlfiles/index.html
+coverage/htmlfiles/jquery-1.4.3.min.js
+coverage/htmlfiles/jquery.hotkeys.js
+coverage/htmlfiles/jquery.isonscreen.js
+coverage/htmlfiles/jquery.tablesorter.min.js
+coverage/htmlfiles/keybd_closed.png
+coverage/htmlfiles/keybd_open.png
+coverage/htmlfiles/pyfile.html
+coverage/htmlfiles/style.css
+doc/api.rst
+doc/branch.rst
+doc/changes.rst
+doc/cmd.rst
+doc/config.rst
+doc/contributing.rst
+doc/excluding.rst
+doc/faq.rst
+doc/index.rst
+doc/install.rst
+doc/source.rst
+doc/subprocess.rst
+doc/trouble.rst
+test/__init__.py
+test/backtest.py
+test/backunittest.py
+test/coveragetest.py
+test/covmodzip1.py
+test/osinfo.py
+test/stress_phystoken.tok
+test/stress_phystoken_dos.tok
+test/test_api.py
+test/test_arcs.py
+test/test_cmdline.py
+test/test_codeunit.py
+test/test_config.py
+test/test_coverage.py
+test/test_data.py
+test/test_execfile.py
+test/test_farm.py
+test/test_files.py
+test/test_html.py
+test/test_misc.py
+test/test_oddball.py
+test/test_parser.py
+test/test_phystokens.py
+test/test_process.py
+test/test_results.py
+test/test_summary.py
+test/test_templite.py
+test/test_testing.py
+test/test_xml.py
+test/try_execfile.py
+test/eggsrc/setup.py
+test/eggsrc/egg1/__init__.py
+test/eggsrc/egg1/egg1.py
+test/farm/annotate/annotate_dir.py
+test/farm/annotate/run.py
+test/farm/annotate/run_multi.py
+test/farm/annotate/gold/white.py,cover
+test/farm/annotate/gold_anno_dir/a___init__.py,cover
+test/farm/annotate/gold_anno_dir/a_a.py,cover
+test/farm/annotate/gold_anno_dir/b___init__.py,cover
+test/farm/annotate/gold_anno_dir/b_b.py,cover
+test/farm/annotate/gold_anno_dir/multi.py,cover
+test/farm/annotate/gold_multi/multi.py,cover
+test/farm/annotate/gold_multi/a/__init__.py,cover
+test/farm/annotate/gold_multi/a/a.py,cover
+test/farm/annotate/gold_multi/b/__init__.py,cover
+test/farm/annotate/gold_multi/b/b.py,cover
+test/farm/annotate/gold_v24/white.py,cover
+test/farm/annotate/src/multi.py
+test/farm/annotate/src/white.py
+test/farm/annotate/src/a/__init__.py
+test/farm/annotate/src/a/a.py
+test/farm/annotate/src/b/__init__.py
+test/farm/annotate/src/b/b.py
+test/farm/html/run_a.py
+test/farm/html/run_a_xml_1.py
+test/farm/html/run_a_xml_2.py
+test/farm/html/run_b_branch.py
+test/farm/html/run_bom.py
+test/farm/html/run_isolatin1.py
+test/farm/html/run_omit_1.py
+test/farm/html/run_omit_2.py
+test/farm/html/run_omit_3.py
+test/farm/html/run_omit_4.py
+test/farm/html/run_omit_5.py
+test/farm/html/run_other.py
+test/farm/html/run_partial.py
+test/farm/html/run_styled.py
+test/farm/html/run_tabbed.py
+test/farm/html/run_unicode.py
+test/farm/html/run_y_xml_branch.py
+test/farm/html/gold_a/a.html
+test/farm/html/gold_a/index.html
+test/farm/html/gold_b_branch/b.html
+test/farm/html/gold_b_branch/index.html
+test/farm/html/gold_bom/bom.html
+test/farm/html/gold_bom/index.html
+test/farm/html/gold_isolatin1/index.html
+test/farm/html/gold_isolatin1/isolatin1.html
+test/farm/html/gold_omit_1/index.html
+test/farm/html/gold_omit_1/m1.html
+test/farm/html/gold_omit_1/m2.html
+test/farm/html/gold_omit_1/m3.html
+test/farm/html/gold_omit_1/main.html
+test/farm/html/gold_omit_2/index.html
+test/farm/html/gold_omit_2/m2.html
+test/farm/html/gold_omit_2/m3.html
+test/farm/html/gold_omit_2/main.html
+test/farm/html/gold_omit_3/index.html
+test/farm/html/gold_omit_3/m3.html
+test/farm/html/gold_omit_3/main.html
+test/farm/html/gold_omit_4/index.html
+test/farm/html/gold_omit_4/m1.html
+test/farm/html/gold_omit_4/m3.html
+test/farm/html/gold_omit_4/main.html
+test/farm/html/gold_omit_5/index.html
+test/farm/html/gold_omit_5/m1.html
+test/farm/html/gold_omit_5/main.html
+test/farm/html/gold_other/blah_blah_other.html
+test/farm/html/gold_other/here.html
+test/farm/html/gold_other/index.html
+test/farm/html/gold_partial/index.html
+test/farm/html/gold_partial/partial.html
+test/farm/html/gold_styled/a.html
+test/farm/html/gold_styled/extra.css
+test/farm/html/gold_styled/index.html
+test/farm/html/gold_styled/style.css
+test/farm/html/gold_unicode/index.html
+test/farm/html/gold_unicode/unicode.html
+test/farm/html/gold_x_xml/coverage.xml
+test/farm/html/gold_y_xml_branch/coverage.xml
+test/farm/html/othersrc/other.py
+test/farm/html/src/a.py
+test/farm/html/src/b.py
+test/farm/html/src/bom.py
+test/farm/html/src/coverage.xml
+test/farm/html/src/extra.css
+test/farm/html/src/here.py
+test/farm/html/src/isolatin1.py
+test/farm/html/src/m1.py
+test/farm/html/src/m2.py
+test/farm/html/src/m3.py
+test/farm/html/src/main.py
+test/farm/html/src/omit4.ini
+test/farm/html/src/omit5.ini
+test/farm/html/src/partial.py
+test/farm/html/src/run_a_xml_2.ini
+test/farm/html/src/tabbed.py
+test/farm/html/src/unicode.py
+test/farm/html/src/y.py
+test/farm/run/run_chdir.py
+test/farm/run/run_timid.py
+test/farm/run/run_xxx.py
+test/farm/run/src/chdir.py
+test/farm/run/src/showtrace.py
+test/farm/run/src/xxx
+test/farm/run/src/subdir/placeholder
+test/js/index.html
+test/js/tests.js
+test/modules/covmod1.py
+test/modules/runmod1.py
+test/modules/usepkgs.py
+test/modules/aa/__init__.py
+test/modules/aa/afile.odd.py
+test/modules/aa/afile.py
+test/modules/aa/zfile.py
+test/modules/aa/bb/__init__.py
+test/modules/aa/bb/bfile.odd.py
+test/modules/aa/bb/bfile.py
+test/modules/aa/bb.odd/bfile.py
+test/modules/aa/bb/cc/__init__.py
+test/modules/aa/bb/cc/cfile.py
+test/modules/pkg1/__init__.py
+test/modules/pkg1/__main__.py
+test/modules/pkg1/p1a.py
+test/modules/pkg1/p1b.py
+test/modules/pkg1/p1c.py
+test/modules/pkg1/runmod2.py
+test/modules/pkg1/sub/__init__.py
+test/modules/pkg1/sub/__main__.py
+test/modules/pkg1/sub/ps1a.py
+test/modules/pkg1/sub/runmod3.py
+test/modules/pkg2/__init__.py
+test/modules/pkg2/p2a.py
+test/modules/pkg2/p2b.py
+test/moremodules/othermods/__init__.py
+test/moremodules/othermods/othera.py
+test/moremodules/othermods/otherb.py
+test/moremodules/othermods/sub/__init__.py
+test/moremodules/othermods/sub/osa.py
+test/moremodules/othermods/sub/osb.py
+test/qunit/jquery.tmpl.min.js
+test/qunit/qunit.css
+test/qunit/qunit.js
\ No newline at end of file
diff --git a/coverage.egg-info/dependency_links.txt b/coverage.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/coverage.egg-info/entry_points.txt b/coverage.egg-info/entry_points.txt
new file mode 100644 (file)
index 0000000..90d812f
--- /dev/null
@@ -0,0 +1,5 @@
+[console_scripts]
+coverage2 = coverage:main
+coverage-2.7 = coverage:main
+coverage = coverage:main
+
diff --git a/coverage.egg-info/not-zip-safe b/coverage.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/coverage.egg-info/top_level.txt b/coverage.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..4ebc8ae
--- /dev/null
@@ -0,0 +1 @@
+coverage
diff --git a/coverage/__init__.py b/coverage/__init__.py
new file mode 100644 (file)
index 0000000..0ccc699
--- /dev/null
@@ -0,0 +1,120 @@
+"""Code coverage measurement for Python.
+
+Ned Batchelder
+http://nedbatchelder.com/code/coverage
+
+"""
+
+from coverage.version import __version__, __url__
+
+from coverage.control import coverage, process_startup
+from coverage.data import CoverageData
+from coverage.cmdline import main, CoverageScript
+from coverage.misc import CoverageException
+
+# Module-level functions.  The original API to this module was based on
+# functions defined directly in the module, with a singleton of the coverage()
+# class.  That design hampered programmability, so the current api uses
+# explicitly-created coverage objects.  But for backward compatibility, here we
+# define the top-level functions to create the singleton when they are first
+# called.
+
+# Singleton object for use with module-level functions.  The singleton is
+# created as needed when one of the module-level functions is called.
+_the_coverage = None
+
+def _singleton_method(name):
+    """Return a function to the `name` method on a singleton `coverage` object.
+
+    The singleton object is created the first time one of these functions is
+    called.
+
+    """
+    # Disable pylint msg W0612, because a bunch of variables look unused, but
+    # they're accessed via locals().
+    # pylint: disable=W0612
+
+    def wrapper(*args, **kwargs):
+        """Singleton wrapper around a coverage method."""
+        global _the_coverage
+        if not _the_coverage:
+            _the_coverage = coverage(auto_data=True)
+        return getattr(_the_coverage, name)(*args, **kwargs)
+
+    import inspect
+    meth = getattr(coverage, name)
+    args, varargs, kw, defaults = inspect.getargspec(meth)
+    argspec = inspect.formatargspec(args[1:], varargs, kw, defaults)
+    docstring = meth.__doc__
+    wrapper.__doc__ = ("""\
+        A first-use-singleton wrapper around coverage.%(name)s.
+
+        This wrapper is provided for backward compatibility with legacy code.
+        New code should use coverage.%(name)s directly.
+
+        %(name)s%(argspec)s:
+
+        %(docstring)s
+        """ % locals()
+        )
+
+    return wrapper
+
+
+# Define the module-level functions.
+use_cache = _singleton_method('use_cache')
+start =     _singleton_method('start')
+stop =      _singleton_method('stop')
+erase =     _singleton_method('erase')
+exclude =   _singleton_method('exclude')
+analysis =  _singleton_method('analysis')
+analysis2 = _singleton_method('analysis2')
+report =    _singleton_method('report')
+annotate =  _singleton_method('annotate')
+
+
+# On Windows, we encode and decode deep enough that something goes wrong and
+# the encodings.utf_8 module is loaded and then unloaded, I don't know why.
+# Adding a reference here prevents it from being unloaded.  Yuk.
+import encodings.utf_8
+
+# Because of the "from coverage.control import fooey" lines at the top of the
+# file, there's an entry for coverage.coverage in sys.modules, mapped to None.
+# This makes some inspection tools (like pydoc) unable to find the class
+# coverage.coverage.  So remove that entry.
+import sys
+try:
+    del sys.modules['coverage.coverage']
+except KeyError:
+    pass
+
+
+# COPYRIGHT AND LICENSE
+#
+# Copyright 2001 Gareth Rees.  All rights reserved.
+# Copyright 2004-2012 Ned Batchelder.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the
+#    distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
diff --git a/coverage/__main__.py b/coverage/__main__.py
new file mode 100644 (file)
index 0000000..55e0d25
--- /dev/null
@@ -0,0 +1,4 @@
+"""Coverage.py's main entry point."""
+import sys
+from coverage.cmdline import main
+sys.exit(main())
diff --git a/coverage/annotate.py b/coverage/annotate.py
new file mode 100644 (file)
index 0000000..b7f32c1
--- /dev/null
@@ -0,0 +1,101 @@
+"""Source file annotation for Coverage."""
+
+import os, re
+
+from coverage.report import Reporter
+
+class AnnotateReporter(Reporter):
+    """Generate annotated source files showing line coverage.
+
+    This reporter creates annotated copies of the measured source files. Each
+    .py file is copied as a .py,cover file, with a left-hand margin annotating
+    each line::
+
+        > def h(x):
+        -     if 0:   #pragma: no cover
+        -         pass
+        >     if x == 1:
+        !         a = 1
+        >     else:
+        >         a = 2
+
+        > h(2)
+
+    Executed lines use '>', lines not executed use '!', lines excluded from
+    consideration use '-'.
+
+    """
+
+    def __init__(self, coverage, config):
+        super(AnnotateReporter, self).__init__(coverage, config)
+        self.directory = None
+
+    blank_re = re.compile(r"\s*(#|$)")
+    else_re = re.compile(r"\s*else\s*:\s*(#|$)")
+
+    def report(self, morfs, directory=None):
+        """Run the report.
+
+        See `coverage.report()` for arguments.
+
+        """
+        self.report_files(self.annotate_file, morfs, directory)
+
+    def annotate_file(self, cu, analysis):
+        """Annotate a single file.
+
+        `cu` is the CodeUnit for the file to annotate.
+
+        """
+        if not cu.relative:
+            return
+
+        filename = cu.filename
+        source = cu.source_file()
+        if self.directory:
+            dest_file = os.path.join(self.directory, cu.flat_rootname())
+            dest_file += ".py,cover"
+        else:
+            dest_file = filename + ",cover"
+        dest = open(dest_file, 'w')
+
+        statements = analysis.statements
+        missing = analysis.missing
+        excluded = analysis.excluded
+
+        lineno = 0
+        i = 0
+        j = 0
+        covered = True
+        while True:
+            line = source.readline()
+            if line == '':
+                break
+            lineno += 1
+            while i < len(statements) and statements[i] < lineno:
+                i += 1
+            while j < len(missing) and missing[j] < lineno:
+                j += 1
+            if i < len(statements) and statements[i] == lineno:
+                covered = j >= len(missing) or missing[j] > lineno
+            if self.blank_re.match(line):
+                dest.write('  ')
+            elif self.else_re.match(line):
+                # Special logic for lines containing only 'else:'.
+                if i >= len(statements) and j >= len(missing):
+                    dest.write('! ')
+                elif i >= len(statements) or j >= len(missing):
+                    dest.write('> ')
+                elif statements[i] == missing[j]:
+                    dest.write('! ')
+                else:
+                    dest.write('> ')
+            elif lineno in excluded:
+                dest.write('- ')
+            elif covered:
+                dest.write('> ')
+            else:
+                dest.write('! ')
+            dest.write(line)
+        source.close()
+        dest.close()
diff --git a/coverage/backward.py b/coverage/backward.py
new file mode 100644 (file)
index 0000000..2c015af
--- /dev/null
@@ -0,0 +1,147 @@
+"""Add things to old Pythons so I can pretend they are newer."""
+
+# This file does lots of tricky stuff, so disable a bunch of lintisms.
+# pylint: disable=F0401,W0611,W0622
+# F0401: Unable to import blah
+# W0611: Unused import blah
+# W0622: Redefining built-in blah
+
+import os, re, sys
+
+# Python 2.3 doesn't have `set`
+try:
+    set = set       # new in 2.4
+except NameError:
+    from sets import Set as set
+
+# Python 2.3 doesn't have `sorted`.
+try:
+    sorted = sorted
+except NameError:
+    def sorted(iterable):
+        """A 2.3-compatible implementation of `sorted`."""
+        lst = list(iterable)
+        lst.sort()
+        return lst
+
+# rpartition is new in 2.5
+try:
+    "".rpartition
+except AttributeError:
+    def rpartition(s, sep):
+        """Implement s.rpartition(sep) for old Pythons."""
+        i = s.rfind(sep)
+        if i == -1:
+            return ('', '', s)
+        else:
+            return (s[:i], sep, s[i+len(sep):])
+else:
+    def rpartition(s, sep):
+        """A common interface for new Pythons."""
+        return s.rpartition(sep)
+
+# Pythons 2 and 3 differ on where to get StringIO
+try:
+    from cStringIO import StringIO
+    BytesIO = StringIO
+except ImportError:
+    from io import StringIO, BytesIO
+
+# What's a string called?
+try:
+    string_class = basestring
+except NameError:
+    string_class = str
+
+# Where do pickles come from?
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+# range or xrange?
+try:
+    range = xrange
+except NameError:
+    range = range
+
+# A function to iterate listlessly over a dict's items.
+if "iteritems" in dir({}):
+    def iitems(d):
+        """Produce the items from dict `d`."""
+        return d.iteritems()
+else:
+    def iitems(d):
+        """Produce the items from dict `d`."""
+        return d.items()
+
+# Exec is a statement in Py2, a function in Py3
+if sys.version_info >= (3, 0):
+    def exec_code_object(code, global_map):
+        """A wrapper around exec()."""
+        exec(code, global_map)
+else:
+    # OK, this is pretty gross.  In Py2, exec was a statement, but that will
+    # be a syntax error if we try to put it in a Py3 file, even if it is never
+    # executed.  So hide it inside an evaluated string literal instead.
+    eval(
+        compile(
+            "def exec_code_object(code, global_map):\n"
+            "    exec code in global_map\n",
+            "<exec_function>", "exec"
+            )
+        )
+
+# Reading Python source and interpreting the coding comment is a big deal.
+if sys.version_info >= (3, 0):
+    # Python 3.2 provides `tokenize.open`, the best way to open source files.
+    import tokenize
+    try:
+        open_source = tokenize.open     # pylint: disable=E1101
+    except AttributeError:
+        from io import TextIOWrapper
+        detect_encoding = tokenize.detect_encoding  # pylint: disable=E1101
+        # Copied from the 3.2 stdlib:
+        def open_source(fname):
+            """Open a file in read only mode using the encoding detected by
+            detect_encoding().
+            """
+            buffer = open(fname, 'rb')
+            encoding, _ = detect_encoding(buffer.readline)
+            buffer.seek(0)
+            text = TextIOWrapper(buffer, encoding, line_buffering=True)
+            text.mode = 'r'
+            return text
+else:
+    def open_source(fname):
+        """Open a source file the best way."""
+        return open(fname, "rU")
+
+
+# Python 3.x is picky about bytes and strings, so provide methods to
+# get them right, and make them no-ops in 2.x
+if sys.version_info >= (3, 0):
+    def to_bytes(s):
+        """Convert string `s` to bytes."""
+        return s.encode('utf8')
+
+    def to_string(b):
+        """Convert bytes `b` to a string."""
+        return b.decode('utf8')
+
+else:
+    def to_bytes(s):
+        """Convert string `s` to bytes (no-op in 2.x)."""
+        return s
+
+    def to_string(b):
+        """Convert bytes `b` to a string (no-op in 2.x)."""
+        return b
+
+# Md5 is available in different places.
+try:
+    import hashlib
+    md5 = hashlib.md5
+except ImportError:
+    import md5
+    md5 = md5.new
diff --git a/coverage/bytecode.py b/coverage/bytecode.py
new file mode 100644 (file)
index 0000000..fd5c7da
--- /dev/null
@@ -0,0 +1,91 @@
+"""Bytecode manipulation for coverage.py"""
+
+import opcode, sys, types
+
+class ByteCode(object):
+    """A single bytecode."""
+    def __init__(self):
+        # The offset of this bytecode in the code object.
+        self.offset = -1
+
+        # The opcode, defined in the `opcode` module.
+        self.op = -1
+
+        # The argument, a small integer, whose meaning depends on the opcode.
+        self.arg = -1
+
+        # The offset in the code object of the next bytecode.
+        self.next_offset = -1
+
+        # The offset to jump to.
+        self.jump_to = -1
+
+
+class ByteCodes(object):
+    """Iterator over byte codes in `code`.
+
+    Returns `ByteCode` objects.
+
+    """
+    # pylint: disable=R0924
+    def __init__(self, code):
+        self.code = code
+        self.offset = 0
+
+    if sys.version_info >= (3, 0):
+        def __getitem__(self, i):
+            return self.code[i]
+    else:
+        def __getitem__(self, i):
+            return ord(self.code[i])
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        if self.offset >= len(self.code):
+            raise StopIteration
+
+        bc = ByteCode()
+        bc.op = self[self.offset]
+        bc.offset = self.offset
+
+        next_offset = self.offset+1
+        if bc.op >= opcode.HAVE_ARGUMENT:
+            bc.arg = self[self.offset+1] + 256*self[self.offset+2]
+            next_offset += 2
+
+            label = -1
+            if bc.op in opcode.hasjrel:
+                label = next_offset + bc.arg
+            elif bc.op in opcode.hasjabs:
+                label = bc.arg
+            bc.jump_to = label
+
+        bc.next_offset = self.offset = next_offset
+        return bc
+
+    next = __next__     # Py2k uses an old-style non-dunder name.
+
+
+class CodeObjects(object):
+    """Iterate over all the code objects in `code`."""
+    def __init__(self, code):
+        self.stack = [code]
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        if self.stack:
+            # We're going to return the code object on the stack, but first
+            # push its children for later returning.
+            code = self.stack.pop()
+            for c in code.co_consts:
+                if isinstance(c, types.CodeType):
+                    self.stack.append(c)
+            return code
+
+        raise StopIteration
+
+    next = __next__
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
new file mode 100644 (file)
index 0000000..cb1d7a3
--- /dev/null
@@ -0,0 +1,730 @@
+"""Command-line support for Coverage."""
+
+import optparse, sys, traceback
+
+from coverage.backward import sorted                # pylint: disable=W0622
+from coverage.execfile import run_python_file, run_python_module
+from coverage.misc import CoverageException, ExceptionDuringRun, NoSource
+
+
+class Opts(object):
+    """A namespace class for individual options we'll build parsers from."""
+
+    append = optparse.make_option(
+        '-a', '--append', action='store_false', dest="erase_first",
+        help="Append coverage data to .coverage, otherwise it is started "
+                "clean with each run."
+        )
+    branch = optparse.make_option(
+        '', '--branch', action='store_true',
+        help="Measure branch coverage in addition to statement coverage."
+        )
+    directory = optparse.make_option(
+        '-d', '--directory', action='store', metavar="DIR",
+        help="Write the output files to DIR."
+        )
+    fail_under = optparse.make_option(
+        '', '--fail-under', action='store', metavar="MIN", type="int",
+        help="Exit with a status of 2 if the total coverage is less than MIN."
+        )
+    help = optparse.make_option(
+        '-h', '--help', action='store_true',
+        help="Get help on this command."
+        )
+    ignore_errors = optparse.make_option(
+        '-i', '--ignore-errors', action='store_true',
+        help="Ignore errors while reading source files."
+        )
+    include = optparse.make_option(
+        '', '--include', action='store',
+        metavar="PAT1,PAT2,...",
+        help="Include files only when their filename path matches one of "
+                "these patterns.  Usually needs quoting on the command line."
+        )
+    pylib = optparse.make_option(
+        '-L', '--pylib', action='store_true',
+        help="Measure coverage even inside the Python installed library, "
+                "which isn't done by default."
+        )
+    show_missing = optparse.make_option(
+        '-m', '--show-missing', action='store_true',
+        help="Show line numbers of statements in each module that weren't "
+                "executed."
+        )
+    old_omit = optparse.make_option(
+        '-o', '--omit', action='store',
+        metavar="PAT1,PAT2,...",
+        help="Omit files when their filename matches one of these patterns. "
+                "Usually needs quoting on the command line."
+        )
+    omit = optparse.make_option(
+        '', '--omit', action='store',
+        metavar="PAT1,PAT2,...",
+        help="Omit files when their filename matches one of these patterns. "
+                "Usually needs quoting on the command line."
+        )
+    output_xml = optparse.make_option(
+        '-o', '', action='store', dest="outfile",
+        metavar="OUTFILE",
+        help="Write the XML report to this file. Defaults to 'coverage.xml'"
+        )
+    parallel_mode = optparse.make_option(
+        '-p', '--parallel-mode', action='store_true',
+        help="Append the machine name, process id and random number to the "
+                ".coverage data file name to simplify collecting data from "
+                "many processes."
+        )
+    module = optparse.make_option(
+        '-m', '--module', action='store_true',
+        help="<pyfile> is an importable Python module, not a script path, "
+                "to be run as 'python -m' would run it."
+        )
+    rcfile = optparse.make_option(
+        '', '--rcfile', action='store',
+        help="Specify configuration file.  Defaults to '.coveragerc'"
+        )
+    source = optparse.make_option(
+        '', '--source', action='store', metavar="SRC1,SRC2,...",
+        help="A list of packages or directories of code to be measured."
+        )
+    timid = optparse.make_option(
+        '', '--timid', action='store_true',
+        help="Use a simpler but slower trace method.  Try this if you get "
+                "seemingly impossible results!"
+        )
+    title = optparse.make_option(
+        '', '--title', action='store', metavar="TITLE",
+        help="A text string to use as the title on the HTML."
+        )
+    version = optparse.make_option(
+        '', '--version', action='store_true',
+        help="Display version information and exit."
+        )
+
+
+class CoverageOptionParser(optparse.OptionParser, object):
+    """Base OptionParser for coverage.
+
+    Problems don't exit the program.
+    Defaults are initialized for all options.
+
+    """
+
+    def __init__(self, *args, **kwargs):
+        super(CoverageOptionParser, self).__init__(
+            add_help_option=False, *args, **kwargs
+            )
+        self.set_defaults(
+            actions=[],
+            branch=None,
+            directory=None,
+            fail_under=None,
+            help=None,
+            ignore_errors=None,
+            include=None,
+            omit=None,
+            parallel_mode=None,
+            module=None,
+            pylib=None,
+            rcfile=True,
+            show_missing=None,
+            source=None,
+            timid=None,
+            title=None,
+            erase_first=None,
+            version=None,
+            )
+
+        self.disable_interspersed_args()
+        self.help_fn = self.help_noop
+
+    def help_noop(self, error=None, topic=None, parser=None):
+        """No-op help function."""
+        pass
+
+    class OptionParserError(Exception):
+        """Used to stop the optparse error handler ending the process."""
+        pass
+
+    def parse_args(self, args=None, options=None):
+        """Call optparse.parse_args, but return a triple:
+
+        (ok, options, args)
+
+        """
+        try:
+            options, args = \
+                super(CoverageOptionParser, self).parse_args(args, options)
+        except self.OptionParserError:
+            return False, None, None
+        return True, options, args
+
+    def error(self, msg):
+        """Override optparse.error so sys.exit doesn't get called."""
+        self.help_fn(msg)
+        raise self.OptionParserError
+
+
+class ClassicOptionParser(CoverageOptionParser):
+    """Command-line parser for coverage.py classic arguments."""
+
+    def __init__(self):
+        super(ClassicOptionParser, self).__init__()
+
+        self.add_action('-a', '--annotate', 'annotate')
+        self.add_action('-b', '--html', 'html')
+        self.add_action('-c', '--combine', 'combine')
+        self.add_action('-e', '--erase', 'erase')
+        self.add_action('-r', '--report', 'report')
+        self.add_action('-x', '--execute', 'execute')
+
+        self.add_options([
+            Opts.directory,
+            Opts.help,
+            Opts.ignore_errors,
+            Opts.pylib,
+            Opts.show_missing,
+            Opts.old_omit,
+            Opts.parallel_mode,
+            Opts.timid,
+            Opts.version,
+        ])
+
+    def add_action(self, dash, dashdash, action_code):
+        """Add a specialized option that is the action to execute."""
+        option = self.add_option(dash, dashdash, action='callback',
+            callback=self._append_action
+            )
+        option.action_code = action_code
+
+    def _append_action(self, option, opt_unused, value_unused, parser):
+        """Callback for an option that adds to the `actions` list."""
+        parser.values.actions.append(option.action_code)
+
+
+class CmdOptionParser(CoverageOptionParser):
+    """Parse one of the new-style commands for coverage.py."""
+
+    def __init__(self, action, options=None, defaults=None, usage=None,
+                cmd=None, description=None
+                ):
+        """Create an OptionParser for a coverage command.
+
+        `action` is the slug to put into `options.actions`.
+        `options` is a list of Option's for the command.
+        `defaults` is a dict of default value for options.
+        `usage` is the usage string to display in help.
+        `cmd` is the command name, if different than `action`.
+        `description` is the description of the command, for the help text.
+
+        """
+        if usage:
+            usage = "%prog " + usage
+        super(CmdOptionParser, self).__init__(
+            prog="coverage %s" % (cmd or action),
+            usage=usage,
+            description=description,
+        )
+        self.set_defaults(actions=[action], **(defaults or {}))
+        if options:
+            self.add_options(options)
+        self.cmd = cmd or action
+
+    def __eq__(self, other):
+        # A convenience equality, so that I can put strings in unit test
+        # results, and they will compare equal to objects.
+        return (other == "<CmdOptionParser:%s>" % self.cmd)
+
+GLOBAL_ARGS = [
+    Opts.rcfile,
+    Opts.help,
+    ]
+
+CMDS = {
+    'annotate': CmdOptionParser("annotate",
+        [
+            Opts.directory,
+            Opts.ignore_errors,
+            Opts.omit,
+            Opts.include,
+            ] + GLOBAL_ARGS,
+        usage = "[options] [modules]",
+        description = "Make annotated copies of the given files, marking "
+            "statements that are executed with > and statements that are "
+            "missed with !."
+        ),
+
+    'combine': CmdOptionParser("combine", GLOBAL_ARGS,
+        usage = " ",
+        description = "Combine data from multiple coverage files collected "
+            "with 'run -p'.  The combined results are written to a single "
+            "file representing the union of the data."
+        ),
+
+    'debug': CmdOptionParser("debug", GLOBAL_ARGS,
+        usage = "<topic>",
+        description = "Display information on the internals of coverage.py, "
+            "for diagnosing problems. "
+            "Topics are 'data' to show a summary of the collected data, "
+            "or 'sys' to show installation information."
+        ),
+
+    'erase': CmdOptionParser("erase", GLOBAL_ARGS,
+        usage = " ",
+        description = "Erase previously collected coverage data."
+        ),
+
+    'help': CmdOptionParser("help", GLOBAL_ARGS,
+        usage = "[command]",
+        description = "Describe how to use coverage.py"
+        ),
+
+    'html': CmdOptionParser("html",
+        [
+            Opts.directory,
+            Opts.fail_under,
+            Opts.ignore_errors,
+            Opts.omit,
+            Opts.include,
+            Opts.title,
+            ] + GLOBAL_ARGS,
+        usage = "[options] [modules]",
+        description = "Create an HTML report of the coverage of the files.  "
+            "Each file gets its own page, with the source decorated to show "
+            "executed, excluded, and missed lines."
+        ),
+
+    'report': CmdOptionParser("report",
+        [
+            Opts.fail_under,
+            Opts.ignore_errors,
+            Opts.omit,
+            Opts.include,
+            Opts.show_missing,
+            ] + GLOBAL_ARGS,
+        usage = "[options] [modules]",
+        description = "Report coverage statistics on modules."
+        ),
+
+    'run': CmdOptionParser("execute",
+        [
+            Opts.append,
+            Opts.branch,
+            Opts.pylib,
+            Opts.parallel_mode,
+            Opts.module,
+            Opts.timid,
+            Opts.source,
+            Opts.omit,
+            Opts.include,
+            ] + GLOBAL_ARGS,
+        defaults = {'erase_first': True},
+        cmd = "run",
+        usage = "[options] <pyfile> [program options]",
+        description = "Run a Python program, measuring code execution."
+        ),
+
+    'xml': CmdOptionParser("xml",
+        [
+            Opts.fail_under,
+            Opts.ignore_errors,
+            Opts.omit,
+            Opts.include,
+            Opts.output_xml,
+            ] + GLOBAL_ARGS,
+        cmd = "xml",
+        usage = "[options] [modules]",
+        description = "Generate an XML report of coverage results."
+        ),
+    }
+
+
+OK, ERR, FAIL_UNDER = 0, 1, 2
+
+
+class CoverageScript(object):
+    """The command-line interface to Coverage."""
+
+    def __init__(self, _covpkg=None, _run_python_file=None,
+                 _run_python_module=None, _help_fn=None):
+        # _covpkg is for dependency injection, so we can test this code.
+        if _covpkg:
+            self.covpkg = _covpkg
+        else:
+            import coverage
+            self.covpkg = coverage
+
+        # For dependency injection:
+        self.run_python_file = _run_python_file or run_python_file
+        self.run_python_module = _run_python_module or run_python_module
+        self.help_fn = _help_fn or self.help
+        self.classic = False
+
+        self.coverage = None
+
+    def command_line(self, argv):
+        """The bulk of the command line interface to Coverage.
+
+        `argv` is the argument list to process.
+
+        Returns 0 if all is well, 1 if something went wrong.
+
+        """
+        # Collect the command-line options.
+        if not argv:
+            self.help_fn(topic='minimum_help')
+            return OK
+
+        # The command syntax we parse depends on the first argument.  Classic
+        # syntax always starts with an option.
+        self.classic = argv[0].startswith('-')
+        if self.classic:
+            parser = ClassicOptionParser()
+        else:
+            parser = CMDS.get(argv[0])
+            if not parser:
+                self.help_fn("Unknown command: '%s'" % argv[0])
+                return ERR
+            argv = argv[1:]
+
+        parser.help_fn = self.help_fn
+        ok, options, args = parser.parse_args(argv)
+        if not ok:
+            return ERR
+
+        # Handle help and version.
+        if self.do_help(options, args, parser):
+            return OK
+
+        # Check for conflicts and problems in the options.
+        if not self.args_ok(options, args):
+            return ERR
+
+        # Listify the list options.
+        source = unshell_list(options.source)
+        omit = unshell_list(options.omit)
+        include = unshell_list(options.include)
+
+        # Do something.
+        self.coverage = self.covpkg.coverage(
+            data_suffix = options.parallel_mode,
+            cover_pylib = options.pylib,
+            timid = options.timid,
+            branch = options.branch,
+            config_file = options.rcfile,
+            source = source,
+            omit = omit,
+            include = include,
+            )
+
+        if 'debug' in options.actions:
+            return self.do_debug(args)
+
+        if 'erase' in options.actions or options.erase_first:
+            self.coverage.erase()
+        else:
+            self.coverage.load()
+
+        if 'execute' in options.actions:
+            self.do_execute(options, args)
+
+        if 'combine' in options.actions:
+            self.coverage.combine()
+            self.coverage.save()
+
+        # Remaining actions are reporting, with some common options.
+        report_args = dict(
+            morfs = args,
+            ignore_errors = options.ignore_errors,
+            omit = omit,
+            include = include,
+            )
+
+        if 'report' in options.actions:
+            total = self.coverage.report(
+                show_missing=options.show_missing, **report_args)
+        if 'annotate' in options.actions:
+            self.coverage.annotate(
+                directory=options.directory, **report_args)
+        if 'html' in options.actions:
+            total = self.coverage.html_report(
+                directory=options.directory, title=options.title,
+                **report_args)
+        if 'xml' in options.actions:
+            outfile = options.outfile
+            total = self.coverage.xml_report(outfile=outfile, **report_args)
+
+        if options.fail_under is not None:
+            if total >= options.fail_under:
+                return OK
+            else:
+                return FAIL_UNDER
+        else:
+            return OK
+
+    def help(self, error=None, topic=None, parser=None):
+        """Display an error message, or the named topic."""
+        assert error or topic or parser
+        if error:
+            print(error)
+            print("Use 'coverage help' for help.")
+        elif parser:
+            print(parser.format_help().strip())
+        else:
+            help_msg = HELP_TOPICS.get(topic, '').strip()
+            if help_msg:
+                print(help_msg % self.covpkg.__dict__)
+            else:
+                print("Don't know topic %r" % topic)
+
+    def do_help(self, options, args, parser):
+        """Deal with help requests.
+
+        Return True if it handled the request, False if not.
+
+        """
+        # Handle help.
+        if options.help:
+            if self.classic:
+                self.help_fn(topic='help')
+            else:
+                self.help_fn(parser=parser)
+            return True
+
+        if "help" in options.actions:
+            if args:
+                for a in args:
+                    parser = CMDS.get(a)
+                    if parser:
+                        self.help_fn(parser=parser)
+                    else:
+                        self.help_fn(topic=a)
+            else:
+                self.help_fn(topic='help')
+            return True
+
+        # Handle version.
+        if options.version:
+            self.help_fn(topic='version')
+            return True
+
+        return False
+
+    def args_ok(self, options, args):
+        """Check for conflicts and problems in the options.
+
+        Returns True if everything is ok, or False if not.
+
+        """
+        for i in ['erase', 'execute']:
+            for j in ['annotate', 'html', 'report', 'combine']:
+                if (i in options.actions) and (j in options.actions):
+                    self.help_fn("You can't specify the '%s' and '%s' "
+                              "options at the same time." % (i, j))
+                    return False
+
+        if not options.actions:
+            self.help_fn(
+                "You must specify at least one of -e, -x, -c, -r, -a, or -b."
+                )
+            return False
+        args_allowed = (
+            'execute' in options.actions or
+            'annotate' in options.actions or
+            'html' in options.actions or
+            'debug' in options.actions or
+            'report' in options.actions or
+            'xml' in options.actions
+            )
+        if not args_allowed and args:
+            self.help_fn("Unexpected arguments: %s" % " ".join(args))
+            return False
+
+        if 'execute' in options.actions and not args:
+            self.help_fn("Nothing to do.")
+            return False
+
+        return True
+
+    def do_execute(self, options, args):
+        """Implementation of 'coverage run'."""
+
+        # Run the script.
+        self.coverage.start()
+        code_ran = True
+        try:
+            try:
+                if options.module:
+                    self.run_python_module(args[0], args)
+                else:
+                    self.run_python_file(args[0], args)
+            except NoSource:
+                code_ran = False
+                raise
+        finally:
+            self.coverage.stop()
+            if code_ran:
+                self.coverage.save()
+
+    def do_debug(self, args):
+        """Implementation of 'coverage debug'."""
+
+        if not args:
+            self.help_fn("What information would you like: data, sys?")
+            return ERR
+        for info in args:
+            if info == 'sys':
+                print("-- sys ----------------------------------------")
+                for label, info in self.coverage.sysinfo():
+                    if info == []:
+                        info = "-none-"
+                    if isinstance(info, list):
+                        prefix = "%15s:" % label
+                        for e in info:
+                            print("%16s %s" % (prefix, e))
+                            prefix = ""
+                    else:
+                        print("%15s: %s" % (label, info))
+            elif info == 'data':
+                print("-- data ---------------------------------------")
+                self.coverage.load()
+                print("path: %s" % self.coverage.data.filename)
+                print("has_arcs: %r" % self.coverage.data.has_arcs())
+                summary = self.coverage.data.summary(fullpath=True)
+                if summary:
+                    filenames = sorted(summary.keys())
+                    print("\n%d files:" % len(filenames))
+                    for f in filenames:
+                        print("%s: %d lines" % (f, summary[f]))
+                else:
+                    print("No data collected")
+            else:
+                self.help_fn("Don't know what you mean by %r" % info)
+                return ERR
+        return OK
+
+
+def unshell_list(s):
+    """Turn a command-line argument into a list."""
+    if not s:
+        return None
+    if sys.platform == 'win32':
+        # When running coverage as coverage.exe, some of the behavior
+        # of the shell is emulated: wildcards are expanded into a list of
+        # filenames.  So you have to single-quote patterns on the command
+        # line, but (not) helpfully, the single quotes are included in the
+        # argument, so we have to strip them off here.
+        s = s.strip("'")
+    return s.split(',')
+
+
+HELP_TOPICS = {
+# -------------------------
+'classic':
+r"""Coverage.py version %(__version__)s
+Measure, collect, and report on code coverage in Python programs.
+
+Usage:
+
+coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
+    Execute the module, passing the given command-line arguments, collecting
+    coverage data.  With the -p option, include the machine name and process
+    id in the .coverage file name.  With -L, measure coverage even inside the
+    Python installed library, which isn't done by default.  With --timid, use a
+    simpler but slower trace method.
+
+coverage -e
+    Erase collected coverage data.
+
+coverage -c
+    Combine data from multiple coverage files (as created by -p option above)
+    and store it into a single file representing the union of the coverage.
+
+coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
+    Report on the statement coverage for the given files.  With the -m
+    option, show line numbers of the statements that weren't executed.
+
+coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...]
+    Create an HTML report of the coverage of the given files.  Each file gets
+    its own page, with the file listing decorated to show executed, excluded,
+    and missed lines.
+
+coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...]
+    Make annotated copies of the given files, marking statements that
+    are executed with > and statements that are missed with !.
+
+-d DIR
+    Write output files for -b or -a to this directory.
+
+-i  Ignore errors while reporting or annotating.
+
+-o DIR,...
+    Omit reporting or annotating files when their filename path starts with
+    a directory listed in the omit list.
+    e.g. coverage -i -r -o c:\python25,lib\enthought\traits
+
+Coverage data is saved in the file .coverage by default.  Set the
+COVERAGE_FILE environment variable to save it somewhere else.
+""",
+# -------------------------
+'help': """\
+Coverage.py, version %(__version__)s
+Measure, collect, and report on code coverage in Python programs.
+
+usage: coverage <command> [options] [args]
+
+Commands:
+    annotate    Annotate source files with execution information.
+    combine     Combine a number of data files.
+    erase       Erase previously collected coverage data.
+    help        Get help on using coverage.py.
+    html        Create an HTML report.
+    report      Report coverage stats on modules.
+    run         Run a Python program and measure code execution.
+    xml         Create an XML report of coverage results.
+
+Use "coverage help <command>" for detailed help on any command.
+Use "coverage help classic" for help on older command syntax.
+For more information, see %(__url__)s
+""",
+# -------------------------
+'minimum_help': """\
+Code coverage for Python.  Use 'coverage help' for help.
+""",
+# -------------------------
+'version': """\
+Coverage.py, version %(__version__)s.  %(__url__)s
+""",
+}
+
+
+def main(argv=None):
+    """The main entry point to Coverage.
+
+    This is installed as the script entry point.
+
+    """
+    if argv is None:
+        argv = sys.argv[1:]
+    try:
+        status = CoverageScript().command_line(argv)
+    except ExceptionDuringRun:
+        # An exception was caught while running the product code.  The
+        # sys.exc_info() return tuple is packed into an ExceptionDuringRun
+        # exception.
+        _, err, _ = sys.exc_info()
+        traceback.print_exception(*err.args)
+        status = ERR
+    except CoverageException:
+        # A controlled error inside coverage.py: print the message to the user.
+        _, err, _ = sys.exc_info()
+        print(err)
+        status = ERR
+    except SystemExit:
+        # The user called `sys.exit()`.  Exit with their argument, if any.
+        _, err, _ = sys.exc_info()
+        if err.args:
+            status = err.args[0]
+        else:
+            status = None
+    return status
diff --git a/coverage/codeunit.py b/coverage/codeunit.py
new file mode 100644 (file)
index 0000000..ca1ae5c
--- /dev/null
@@ -0,0 +1,145 @@
+"""Code unit (module) handling for Coverage."""
+
+import glob, os
+
+from coverage.backward import open_source, string_class, StringIO
+from coverage.misc import CoverageException
+
+
+def code_unit_factory(morfs, file_locator):
+    """Construct a list of CodeUnits from polymorphic inputs.
+
+    `morfs` is a module or a filename, or a list of same.
+
+    `file_locator` is a FileLocator that can help resolve filenames.
+
+    Returns a list of CodeUnit objects.
+
+    """
+    # Be sure we have a list.
+    if not isinstance(morfs, (list, tuple)):
+        morfs = [morfs]
+
+    # On Windows, the shell doesn't expand wildcards.  Do it here.
+    globbed = []
+    for morf in morfs:
+        if isinstance(morf, string_class) and ('?' in morf or '*' in morf):
+            globbed.extend(glob.glob(morf))
+        else:
+            globbed.append(morf)
+    morfs = globbed
+
+    code_units = [CodeUnit(morf, file_locator) for morf in morfs]
+
+    return code_units
+
+
+class CodeUnit(object):
+    """Code unit: a filename or module.
+
+    Instance attributes:
+
+    `name` is a human-readable name for this code unit.
+    `filename` is the os path from which we can read the source.
+    `relative` is a boolean.
+
+    """
+    def __init__(self, morf, file_locator):
+        self.file_locator = file_locator
+
+        if hasattr(morf, '__file__'):
+            f = morf.__file__
+        else:
+            f = morf
+        # .pyc files should always refer to a .py instead.
+        if f.endswith('.pyc') or f.endswith('.pyo'):
+            f = f[:-1]
+        elif f.endswith('$py.class'): # Jython
+            f = f[:-9] + ".py"
+        self.filename = self.file_locator.canonical_filename(f)
+
+        if hasattr(morf, '__name__'):
+            n = modname = morf.__name__
+            self.relative = True
+        else:
+            n = os.path.splitext(morf)[0]
+            rel = self.file_locator.relative_filename(n)
+            if os.path.isabs(n):
+                self.relative = (rel != n)
+            else:
+                self.relative = True
+            n = rel
+            modname = None
+        self.name = n
+        self.modname = modname
+
+    def __repr__(self):
+        return "<CodeUnit name=%r filename=%r>" % (self.name, self.filename)
+
+    # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
+    # of them defined.
+
+    def __lt__(self, other):
+        return self.name < other.name
+    def __le__(self, other):
+        return self.name <= other.name
+    def __eq__(self, other):
+        return self.name == other.name
+    def __ne__(self, other):
+        return self.name != other.name
+    def __gt__(self, other):
+        return self.name > other.name
+    def __ge__(self, other):
+        return self.name >= other.name
+
+    def flat_rootname(self):
+        """A base for a flat filename to correspond to this code unit.
+
+        Useful for writing files about the code where you want all the files in
+        the same directory, but need to differentiate same-named files from
+        different directories.
+
+        For example, the file a/b/c.py might return 'a_b_c'
+
+        """
+        if self.modname:
+            return self.modname.replace('.', '_')
+        else:
+            root = os.path.splitdrive(self.name)[1]
+            return root.replace('\\', '_').replace('/', '_').replace('.', '_')
+
+    def source_file(self):
+        """Return an open file for reading the source of the code unit."""
+        if os.path.exists(self.filename):
+            # A regular text file: open it.
+            return open_source(self.filename)
+
+        # Maybe it's in a zip file?
+        source = self.file_locator.get_zip_data(self.filename)
+        if source is not None:
+            return StringIO(source)
+
+        # Couldn't find source.
+        raise CoverageException(
+            "No source for code '%s'." % self.filename
+            )
+
+    def should_be_python(self):
+        """Does it seem like this file should contain Python?
+
+        This is used to decide if a file reported as part of the exection of
+        a program was really likely to have contained Python in the first
+        place.
+
+        """
+        # Get the file extension.
+        _, ext = os.path.splitext(self.filename)
+
+        # Anything named *.py* should be Python.
+        if ext.startswith('.py'):
+            return True
+        # A file with no extension should be Python.
+        if not ext:
+            return True
+        # Everything else is probably not Python.
+        return False
diff --git a/coverage/collector.py b/coverage/collector.py
new file mode 100644 (file)
index 0000000..96a9661
--- /dev/null
@@ -0,0 +1,340 @@
+"""Raw data collector for Coverage."""
+
+import os, sys, threading
+
+try:
+    # Use the C extension code when we can, for speed.
+    from coverage.tracer import CTracer         # pylint: disable=F0401,E0611
+except ImportError:
+    # Couldn't import the C extension, maybe it isn't built.
+    if os.getenv('COVERAGE_TEST_TRACER') == 'c':
+        # During testing, we use the COVERAGE_TEST_TRACER env var to indicate
+        # that we've fiddled with the environment to test this fallback code.
+        # If we thought we had a C tracer, but couldn't import it, then exit
+        # quickly and clearly instead of dribbling confusing errors. I'm using
+        # sys.exit here instead of an exception because an exception here
+        # causes all sorts of other noise in unittest.
+        sys.stderr.write(
+            "*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n"
+            )
+        sys.exit(1)
+    CTracer = None
+
+
+class PyTracer(object):
+    """Python implementation of the raw data tracer."""
+
+    # Because of poor implementations of trace-function-manipulating tools,
+    # the Python trace function must be kept very simple.  In particular, there
+    # must be only one function ever set as the trace function, both through
+    # sys.settrace, and as the return value from the trace function.  Put
+    # another way, the trace function must always return itself.  It cannot
+    # swap in other functions, or return None to avoid tracing a particular
+    # frame.
+    #
+    # The trace manipulator that introduced this restriction is DecoratorTools,
+    # which sets a trace function, and then later restores the pre-existing one
+    # by calling sys.settrace with a function it found in the current frame.
+    #
+    # Systems that use DecoratorTools (or similar trace manipulations) must use
+    # PyTracer to get accurate results.  The command-line --timid argument is
+    # used to force the use of this tracer.
+
+    def __init__(self):
+        self.data = None
+        self.should_trace = None
+        self.should_trace_cache = None
+        self.warn = None
+        self.cur_file_data = None
+        self.last_line = 0
+        self.data_stack = []
+        self.last_exc_back = None
+        self.last_exc_firstlineno = 0
+        self.arcs = False
+
+    def _trace(self, frame, event, arg_unused):
+        """The trace function passed to sys.settrace."""
+
+        #print("trace event: %s %r @%d" % (
+        #           event, frame.f_code.co_filename, frame.f_lineno),
+        #      file=sys.stderr)
+
+        if self.last_exc_back:
+            if frame == self.last_exc_back:
+                # Someone forgot a return event.
+                if self.arcs and self.cur_file_data:
+                    pair = (self.last_line, -self.last_exc_firstlineno)
+                    self.cur_file_data[pair] = None
+                self.cur_file_data, self.last_line = self.data_stack.pop()
+            self.last_exc_back = None
+
+        if event == 'call':
+            # Entering a new function context.  Decide if we should trace
+            # in this file.
+            self.data_stack.append((self.cur_file_data, self.last_line))
+            filename = frame.f_code.co_filename
+            tracename = self.should_trace_cache.get(filename)
+            if tracename is None:
+                tracename = self.should_trace(filename, frame)
+                self.should_trace_cache[filename] = tracename
+            #print("called, stack is %d deep, tracename is %r" % (
+            #               len(self.data_stack), tracename))
+            if tracename:
+                if tracename not in self.data:
+                    self.data[tracename] = {}
+                self.cur_file_data = self.data[tracename]
+            else:
+                self.cur_file_data = None
+            # Set the last_line to -1 because the next arc will be entering a
+            # code block, indicated by (-1, n).
+            self.last_line = -1
+        elif event == 'line':
+            # Record an executed line.
+            if self.cur_file_data is not None:
+                if self.arcs:
+                    #print("lin", self.last_line, frame.f_lineno)
+                    self.cur_file_data[(self.last_line, frame.f_lineno)] = None
+                else:
+                    #print("lin", frame.f_lineno)
+                    self.cur_file_data[frame.f_lineno] = None
+            self.last_line = frame.f_lineno
+        elif event == 'return':
+            if self.arcs and self.cur_file_data:
+                first = frame.f_code.co_firstlineno
+                self.cur_file_data[(self.last_line, -first)] = None
+            # Leaving this function, pop the filename stack.
+            self.cur_file_data, self.last_line = self.data_stack.pop()
+            #print("returned, stack is %d deep" % (len(self.data_stack)))
+        elif event == 'exception':
+            #print("exc", self.last_line, frame.f_lineno)
+            self.last_exc_back = frame.f_back
+            self.last_exc_firstlineno = frame.f_code.co_firstlineno
+        return self._trace
+
+    def start(self):
+        """Start this Tracer.
+
+        Return a Python function suitable for use with sys.settrace().
+
+        """
+        sys.settrace(self._trace)
+        return self._trace
+
+    def stop(self):
+        """Stop this Tracer."""
+        if hasattr(sys, "gettrace") and self.warn:
+            if sys.gettrace() != self._trace:
+                msg = "Trace function changed, measurement is likely wrong: %r"
+                self.warn(msg % (sys.gettrace(),))
+                #--debug
+                #from coverage.misc import short_stack
+                #self.warn(msg % (sys.gettrace()))#, short_stack()))
+        sys.settrace(None)
+
+    def get_stats(self):
+        """Return a dictionary of statistics, or None."""
+        return None
+
+
+class Collector(object):
+    """Collects trace data.
+
+    Creates a Tracer object for each thread, since they track stack
+    information.  Each Tracer points to the same shared data, contributing
+    traced data points.
+
+    When the Collector is started, it creates a Tracer for the current thread,
+    and installs a function to create Tracers for each new thread started.
+    When the Collector is stopped, all active Tracers are stopped.
+
+    Threads started while the Collector is stopped will never have Tracers
+    associated with them.
+
+    """
+
+    # The stack of active Collectors.  Collectors are added here when started,
+    # and popped when stopped.  Collectors on the stack are paused when not
+    # the top, and resumed when they become the top again.
+    _collectors = []
+
+    def __init__(self, should_trace, timid, branch, warn):
+        """Create a collector.
+
+        `should_trace` is a function, taking a filename, and returning a
+        canonicalized filename, or False depending on whether the file should
+        be traced or not.
+
+        If `timid` is true, then a slower simpler trace function will be
+        used.  This is important for some environments where manipulation of
+        tracing functions make the faster more sophisticated trace function not
+        operate properly.
+
+        If `branch` is true, then branches will be measured.  This involves
+        collecting data on which statements followed each other (arcs).  Use
+        `get_arc_data` to get the arc data.
+
+        `warn` is a warning function, taking a single string message argument,
+        to be used if a warning needs to be issued.
+
+        """
+        self.should_trace = should_trace
+        self.warn = warn
+        self.branch = branch
+        self.reset()
+
+        if timid:
+            # Being timid: use the simple Python trace function.
+            self._trace_class = PyTracer
+        else:
+            # Being fast: use the C Tracer if it is available, else the Python
+            # trace function.
+            self._trace_class = CTracer or PyTracer
+
+    def __repr__(self):
+        return "<Collector at 0x%x>" % id(self)
+
+    def tracer_name(self):
+        """Return the class name of the tracer we're using."""
+        return self._trace_class.__name__
+
+    def reset(self):
+        """Clear collected data, and prepare to collect more."""
+        # A dictionary mapping filenames to dicts with linenumber keys,
+        # or mapping filenames to dicts with linenumber pairs as keys.
+        self.data = {}
+
+        # A cache of the results from should_trace, the decision about whether
+        # to trace execution in a file. A dict of filename to (filename or
+        # False).
+        self.should_trace_cache = {}
+
+        # Our active Tracers.
+        self.tracers = []
+
+    def _start_tracer(self):
+        """Start a new Tracer object, and store it in self.tracers."""
+        tracer = self._trace_class()
+        tracer.data = self.data
+        tracer.arcs = self.branch
+        tracer.should_trace = self.should_trace
+        tracer.should_trace_cache = self.should_trace_cache
+        tracer.warn = self.warn
+        fn = tracer.start()
+        self.tracers.append(tracer)
+        return fn
+
+    # The trace function has to be set individually on each thread before
+    # execution begins.  Ironically, the only support the threading module has
+    # for running code before the thread main is the tracing function.  So we
+    # install this as a trace function, and the first time it's called, it does
+    # the real trace installation.
+
+    def _installation_trace(self, frame_unused, event_unused, arg_unused):
+        """Called on new threads, installs the real tracer."""
+        # Remove ourselves as the trace function
+        sys.settrace(None)
+        # Install the real tracer.
+        fn = self._start_tracer()
+        # Invoke the real trace function with the current event, to be sure
+        # not to lose an event.
+        if fn:
+            fn = fn(frame_unused, event_unused, arg_unused)
+        # Return the new trace function to continue tracing in this scope.
+        return fn
+
+    def start(self):
+        """Start collecting trace information."""
+        if self._collectors:
+            self._collectors[-1].pause()
+        self._collectors.append(self)
+        #print("Started: %r" % self._collectors, file=sys.stderr)
+
+        # Check to see whether we had a fullcoverage tracer installed.
+        traces0 = []
+        if hasattr(sys, "gettrace"):
+            fn0 = sys.gettrace()
+            if fn0:
+                tracer0 = getattr(fn0, '__self__', None)
+                if tracer0:
+                    traces0 = getattr(tracer0, 'traces', [])
+
+        # Install the tracer on this thread.
+        fn = self._start_tracer()
+
+        for args in traces0:
+            (frame, event, arg), lineno = args
+            try:
+                fn(frame, event, arg, lineno=lineno)
+            except TypeError:
+                raise Exception(
+                    "fullcoverage must be run with the C trace function."
+                )
+
+        # Install our installation tracer in threading, to jump start other
+        # threads.
+        threading.settrace(self._installation_trace)
+
+    def stop(self):
+        """Stop collecting trace information."""
+        #print >>sys.stderr, "Stopping: %r" % self._collectors
+        assert self._collectors
+        assert self._collectors[-1] is self
+
+        self.pause()
+        self.tracers = []
+
+        # Remove this Collector from the stack, and resume the one underneath
+        # (if any).
+        self._collectors.pop()
+        if self._collectors:
+            self._collectors[-1].resume()
+
+    def pause(self):
+        """Pause tracing, but be prepared to `resume`."""
+        for tracer in self.tracers:
+            tracer.stop()
+            stats = tracer.get_stats()
+            if stats:
+                print("\nCoverage.py tracer stats:")
+                for k in sorted(stats.keys()):
+                    print("%16s: %s" % (k, stats[k]))
+        threading.settrace(None)
+
+    def resume(self):
+        """Resume tracing after a `pause`."""
+        for tracer in self.tracers:
+            tracer.start()
+        threading.settrace(self._installation_trace)
+
+    def get_line_data(self):
+        """Return the line data collected.
+
+        Data is { filename: { lineno: None, ...}, ...}
+
+        """
+        if self.branch:
+            # If we were measuring branches, then we have to re-build the dict
+            # to show line data.
+            line_data = {}
+            for f, arcs in self.data.items():
+                line_data[f] = ldf = {}
+                for l1, _ in list(arcs.keys()):
+                    if l1:
+                        ldf[l1] = None
+            return line_data
+        else:
+            return self.data
+
+    def get_arc_data(self):
+        """Return the arc data collected.
+
+        Data is { filename: { (l1, l2): None, ...}, ...}
+
+        Note that no data is collected or returned if the Collector wasn't
+        created with `branch` true.
+
+        """
+        if self.branch:
+            return self.data
+        else:
+            return {}
diff --git a/coverage/config.py b/coverage/config.py
new file mode 100644 (file)
index 0000000..c2ebecb
--- /dev/null
@@ -0,0 +1,211 @@
+"""Config file for coverage.py"""
+
+import os, re, sys
+from coverage.backward import string_class, iitems
+
+# In py3, # ConfigParser was renamed to the more-standard configparser
+try:
+    import configparser                             # pylint: disable=F0401
+except ImportError:
+    import ConfigParser as configparser
+
+
+class HandyConfigParser(configparser.RawConfigParser):
+    """Our specialization of ConfigParser."""
+
+    def read(self, filename):
+        """Read a filename as UTF-8 configuration data."""
+        kwargs = {}
+        if sys.version_info >= (3, 2):
+            kwargs['encoding'] = "utf-8"
+        return configparser.RawConfigParser.read(self, filename, **kwargs)
+
+    def get(self, *args, **kwargs):
+        v = configparser.RawConfigParser.get(self, *args, **kwargs)
+        def dollar_replace(m):
+            """Called for each $replacement."""
+            # Only one of the groups will have matched, just get its text.
+            word = [w for w in m.groups() if w is not None][0]
+            if word == "$":
+                return "$"
+            else:
+                return os.environ.get(word, '')
+
+        dollar_pattern = r"""(?x)   # Use extended regex syntax
+            \$(?:                   # A dollar sign, then
+            (?P<v1>\w+) |           #   a plain word,
+            {(?P<v2>\w+)} |         #   or a {-wrapped word,
+            (?P<char>[$])           #   or a dollar sign.
+            )
+            """
+        v = re.sub(dollar_pattern, dollar_replace, v)
+        return v
+
+    def getlist(self, section, option):
+        """Read a list of strings.
+
+        The value of `section` and `option` is treated as a comma- and newline-
+        separated list of strings.  Each value is stripped of whitespace.
+
+        Returns the list of strings.
+
+        """
+        value_list = self.get(section, option)
+        values = []
+        for value_line in value_list.split('\n'):
+            for value in value_line.split(','):
+                value = value.strip()
+                if value:
+                    values.append(value)
+        return values
+
+    def getlinelist(self, section, option):
+        """Read a list of full-line strings.
+
+        The value of `section` and `option` is treated as a newline-separated
+        list of strings.  Each value is stripped of whitespace.
+
+        Returns the list of strings.
+
+        """
+        value_list = self.get(section, option)
+        return list(filter(None, value_list.split('\n')))
+
+
+# The default line exclusion regexes
+DEFAULT_EXCLUDE = [
+    '(?i)# *pragma[: ]*no *cover',
+    ]
+
+# The default partial branch regexes, to be modified by the user.
+DEFAULT_PARTIAL = [
+    '(?i)# *pragma[: ]*no *branch',
+    ]
+
+# The default partial branch regexes, based on Python semantics.
+# These are any Python branching constructs that can't actually execute all
+# their branches.
+DEFAULT_PARTIAL_ALWAYS = [
+    'while (True|1|False|0):',
+    'if (True|1|False|0):',
+    ]
+
+
+class CoverageConfig(object):
+    """Coverage.py configuration.
+
+    The attributes of this class are the various settings that control the
+    operation of coverage.py.
+
+    """
+    def __init__(self):
+        """Initialize the configuration attributes to their defaults."""
+        # Metadata about the config.
+        self.attempted_config_files = []
+        self.config_files = []
+
+        # Defaults for [run]
+        self.branch = False
+        self.cover_pylib = False
+        self.data_file = ".coverage"
+        self.parallel = False
+        self.timid = False
+        self.source = None
+
+        # Defaults for [report]
+        self.exclude_list = DEFAULT_EXCLUDE[:]
+        self.ignore_errors = False
+        self.include = None
+        self.omit = None
+        self.partial_list = DEFAULT_PARTIAL[:]
+        self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
+        self.precision = 0
+        self.show_missing = False
+
+        # Defaults for [html]
+        self.html_dir = "htmlcov"
+        self.extra_css = None
+        self.html_title = "Coverage report"
+
+        # Defaults for [xml]
+        self.xml_output = "coverage.xml"
+
+        # Defaults for [paths]
+        self.paths = {}
+
+    def from_environment(self, env_var):
+        """Read configuration from the `env_var` environment variable."""
+        # Timidity: for nose users, read an environment variable.  This is a
+        # cheap hack, since the rest of the command line arguments aren't
+        # recognized, but it solves some users' problems.
+        env = os.environ.get(env_var, '')
+        if env:
+            self.timid = ('--timid' in env)
+
+    MUST_BE_LIST = ["omit", "include"]
+
+    def from_args(self, **kwargs):
+        """Read config values from `kwargs`."""
+        for k, v in iitems(kwargs):
+            if v is not None:
+                if k in self.MUST_BE_LIST and isinstance(v, string_class):
+                    v = [v]
+                setattr(self, k, v)
+
+    def from_file(self, filename):
+        """Read configuration from a .rc file.
+
+        `filename` is a file name to read.
+
+        """
+        self.attempted_config_files.append(filename)
+
+        cp = HandyConfigParser()
+        files_read = cp.read(filename)
+        if files_read is not None:  # return value changed in 2.4
+            self.config_files.extend(files_read)
+
+        for option_spec in self.CONFIG_FILE_OPTIONS:
+            self.set_attr_from_config_option(cp, *option_spec)
+
+        # [paths] is special
+        if cp.has_section('paths'):
+            for option in cp.options('paths'):
+                self.paths[option] = cp.getlist('paths', option)
+
+    CONFIG_FILE_OPTIONS = [
+        # [run]
+        ('branch', 'run:branch', 'boolean'),
+        ('cover_pylib', 'run:cover_pylib', 'boolean'),
+        ('data_file', 'run:data_file'),
+        ('include', 'run:include', 'list'),
+        ('omit', 'run:omit', 'list'),
+        ('parallel', 'run:parallel', 'boolean'),
+        ('source', 'run:source', 'list'),
+        ('timid', 'run:timid', 'boolean'),
+
+        # [report]
+        ('exclude_list', 'report:exclude_lines', 'linelist'),
+        ('ignore_errors', 'report:ignore_errors', 'boolean'),
+        ('include', 'report:include', 'list'),
+        ('omit', 'report:omit', 'list'),
+        ('partial_list', 'report:partial_branches', 'linelist'),
+        ('partial_always_list', 'report:partial_branches_always', 'linelist'),
+        ('precision', 'report:precision', 'int'),
+        ('show_missing', 'report:show_missing', 'boolean'),
+
+        # [html]
+        ('html_dir', 'html:directory'),
+        ('extra_css', 'html:extra_css'),
+        ('html_title', 'html:title'),
+
+        # [xml]
+        ('xml_output', 'xml:output'),
+        ]
+
+    def set_attr_from_config_option(self, cp, attr, where, type_=''):
+        """Set an attribute on self if it exists in the ConfigParser."""
+        section, option = where.split(":")
+        if cp.has_option(section, option):
+            method = getattr(cp, 'get'+type_)
+            setattr(self, attr, method(section, option))
diff --git a/coverage/control.py b/coverage/control.py
new file mode 100644 (file)
index 0000000..afb6137
--- /dev/null
@@ -0,0 +1,720 @@
+"""Core control stuff for Coverage."""
+
+import atexit, os, random, socket, sys
+
+from coverage.annotate import AnnotateReporter
+from coverage.backward import string_class, iitems
+from coverage.codeunit import code_unit_factory, CodeUnit
+from coverage.collector import Collector
+from coverage.config import CoverageConfig
+from coverage.data import CoverageData
+from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher
+from coverage.files import PathAliases, find_python_files, prep_patterns
+from coverage.html import HtmlReporter
+from coverage.misc import CoverageException, bool_or_none, join_regex
+from coverage.misc import file_be_gone
+from coverage.results import Analysis, Numbers
+from coverage.summary import SummaryReporter
+from coverage.xmlreport import XmlReporter
+
+class coverage(object):
+    """Programmatic access to coverage.py.
+
+    To use::
+
+        from coverage import coverage
+
+        cov = coverage()
+        cov.start()
+        #.. call your code ..
+        cov.stop()
+        cov.html_report(directory='covhtml')
+
+    """
+    def __init__(self, data_file=None, data_suffix=None, cover_pylib=None,
+                auto_data=False, timid=None, branch=None, config_file=True,
+                source=None, omit=None, include=None):
+        """
+        `data_file` is the base name of the data file to use, defaulting to
+        ".coverage".  `data_suffix` is appended (with a dot) to `data_file` to
+        create the final file name.  If `data_suffix` is simply True, then a
+        suffix is created with the machine and process identity included.
+
+        `cover_pylib` is a boolean determining whether Python code installed
+        with the Python interpreter is measured.  This includes the Python
+        standard library and any packages installed with the interpreter.
+
+        If `auto_data` is true, then any existing data file will be read when
+        coverage measurement starts, and data will be saved automatically when
+        measurement stops.
+
+        If `timid` is true, then a slower and simpler trace function will be
+        used.  This is important for some environments where manipulation of
+        tracing functions breaks the faster trace function.
+
+        If `branch` is true, then branch coverage will be measured in addition
+        to the usual statement coverage.
+
+        `config_file` determines what config file to read.  If it is a string,
+        it is the name of the config file to read.  If it is True, then a
+        standard file is read (".coveragerc").  If it is False, then no file is
+        read.
+
+        `source` is a list of file paths or package names.  Only code located
+        in the trees indicated by the file paths or package names will be
+        measured.
+
+        `include` and `omit` are lists of filename patterns. Files that match
+        `include` will be measured, files that match `omit` will not.  Each
+        will also accept a single string argument.
+
+        """
+        from coverage import __version__
+
+        # A record of all the warnings that have been issued.
+        self._warnings = []
+
+        # Build our configuration from a number of sources:
+        # 1: defaults:
+        self.config = CoverageConfig()
+
+        # 2: from the coveragerc file:
+        if config_file:
+            if config_file is True:
+                config_file = ".coveragerc"
+            try:
+                self.config.from_file(config_file)
+            except ValueError:
+                _, err, _ = sys.exc_info()
+                raise CoverageException(
+                    "Couldn't read config file %s: %s" % (config_file, err)
+                    )
+
+        # 3: from environment variables:
+        self.config.from_environment('COVERAGE_OPTIONS')
+        env_data_file = os.environ.get('COVERAGE_FILE')
+        if env_data_file:
+            self.config.data_file = env_data_file
+
+        # 4: from constructor arguments:
+        self.config.from_args(
+            data_file=data_file, cover_pylib=cover_pylib, timid=timid,
+            branch=branch, parallel=bool_or_none(data_suffix),
+            source=source, omit=omit, include=include
+            )
+
+        self.auto_data = auto_data
+
+        # _exclude_re is a dict mapping exclusion list names to compiled
+        # regexes.
+        self._exclude_re = {}
+        self._exclude_regex_stale()
+
+        self.file_locator = FileLocator()
+
+        # The source argument can be directories or package names.
+        self.source = []
+        self.source_pkgs = []
+        for src in self.config.source or []:
+            if os.path.exists(src):
+                self.source.append(self.file_locator.canonical_filename(src))
+            else:
+                self.source_pkgs.append(src)
+
+        self.omit = prep_patterns(self.config.omit)
+        self.include = prep_patterns(self.config.include)
+
+        self.collector = Collector(
+            self._should_trace, timid=self.config.timid,
+            branch=self.config.branch, warn=self._warn
+            )
+
+        # Suffixes are a bit tricky.  We want to use the data suffix only when
+        # collecting data, not when combining data.  So we save it as
+        # `self.run_suffix` now, and promote it to `self.data_suffix` if we
+        # find that we are collecting data later.
+        if data_suffix or self.config.parallel:
+            if not isinstance(data_suffix, string_class):
+                # if data_suffix=True, use .machinename.pid.random
+                data_suffix = True
+        else:
+            data_suffix = None
+        self.data_suffix = None
+        self.run_suffix = data_suffix
+
+        # Create the data file.  We do this at construction time so that the
+        # data file will be written into the directory where the process
+        # started rather than wherever the process eventually chdir'd to.
+        self.data = CoverageData(
+            basename=self.config.data_file,
+            collector="coverage v%s" % __version__
+            )
+
+        # The dirs for files considered "installed with the interpreter".
+        self.pylib_dirs = []
+        if not self.config.cover_pylib:
+            # Look at where some standard modules are located. That's the
+            # indication for "installed with the interpreter". In some
+            # environments (virtualenv, for example), these modules may be
+            # spread across a few locations. Look at all the candidate modules
+            # we've imported, and take all the different ones.
+            for m in (atexit, os, random, socket):
+                if hasattr(m, "__file__"):
+                    m_dir = self._canonical_dir(m)
+                    if m_dir not in self.pylib_dirs:
+                        self.pylib_dirs.append(m_dir)
+
+        # To avoid tracing the coverage code itself, we skip anything located
+        # where we are.
+        self.cover_dir = self._canonical_dir(__file__)
+
+        # The matchers for _should_trace, created when tracing starts.
+        self.source_match = None
+        self.pylib_match = self.cover_match = None
+        self.include_match = self.omit_match = None
+
+        # Set the reporting precision.
+        Numbers.set_precision(self.config.precision)
+
+        # Is it ok for no data to be collected?
+        self._warn_no_data = True
+        self._warn_unimported_source = True
+
+        # State machine variables:
+        # Have we started collecting and not stopped it?
+        self._started = False
+        # Have we measured some data and not harvested it?
+        self._measured = False
+
+        atexit.register(self._atexit)
+
+    def _canonical_dir(self, morf):
+        """Return the canonical directory of the module or file `morf`."""
+        return os.path.split(CodeUnit(morf, self.file_locator).filename)[0]
+
+    def _source_for_file(self, filename):
+        """Return the source file for `filename`."""
+        if not filename.endswith(".py"):
+            if filename[-4:-1] == ".py":
+                filename = filename[:-1]
+            elif filename.endswith("$py.class"): # jython
+                filename = filename[:-9] + ".py"
+        return filename
+
+    def _should_trace(self, filename, frame):
+        """Decide whether to trace execution in `filename`
+
+        This function is called from the trace function.  As each new file name
+        is encountered, this function determines whether it is traced or not.
+
+        Returns a canonicalized filename if it should be traced, False if it
+        should not.
+
+        """
+        if not filename:
+            # Empty string is pretty useless
+            return False
+
+        if filename.startswith('<'):
+            # Lots of non-file execution is represented with artificial
+            # filenames like "<string>", "<doctest readme.txt[0]>", or
+            # "<exec_function>".  Don't ever trace these executions, since we
+            # can't do anything with the data later anyway.
+            return False
+
+        self._check_for_packages()
+
+        # Compiled Python files have two filenames: frame.f_code.co_filename is
+        # the filename at the time the .pyc was compiled.  The second name is
+        # __file__, which is where the .pyc was actually loaded from.  Since
+        # .pyc files can be moved after compilation (for example, by being
+        # installed), we look for __file__ in the frame and prefer it to the
+        # co_filename value.
+        dunder_file = frame.f_globals.get('__file__')
+        if dunder_file:
+            filename = self._source_for_file(dunder_file)
+
+        # Jython reports the .class file to the tracer, use the source file.
+        if filename.endswith("$py.class"):
+            filename = filename[:-9] + ".py"
+
+        canonical = self.file_locator.canonical_filename(filename)
+
+        # If the user specified source or include, then that's authoritative
+        # about the outer bound of what to measure and we don't have to apply
+        # any canned exclusions. If they didn't, then we have to exclude the
+        # stdlib and coverage.py directories.
+        if self.source_match:
+            if not self.source_match.match(canonical):
+                return False
+        elif self.include_match:
+            if not self.include_match.match(canonical):
+                return False
+        else:
+            # If we aren't supposed to trace installed code, then check if this
+            # is near the Python standard library and skip it if so.
+            if self.pylib_match and self.pylib_match.match(canonical):
+                return False
+
+            # We exclude the coverage code itself, since a little of it will be
+            # measured otherwise.
+            if self.cover_match and self.cover_match.match(canonical):
+                return False
+
+        # Check the file against the omit pattern.
+        if self.omit_match and self.omit_match.match(canonical):
+            return False
+
+        return canonical
+
+    # To log what should_trace returns, change this to "if 1:"
+    if 0:
+        _real_should_trace = _should_trace
+        def _should_trace(self, filename, frame):   # pylint: disable=E0102
+            """A logging decorator around the real _should_trace function."""
+            ret = self._real_should_trace(filename, frame)
+            print("should_trace: %r -> %r" % (filename, ret))
+            return ret
+
+    def _warn(self, msg):
+        """Use `msg` as a warning."""
+        self._warnings.append(msg)
+        sys.stderr.write("Coverage.py warning: %s\n" % msg)
+
+    def _check_for_packages(self):
+        """Update the source_match matcher with latest imported packages."""
+        # Our self.source_pkgs attribute is a list of package names we want to
+        # measure.  Each time through here, we see if we've imported any of
+        # them yet.  If so, we add its file to source_match, and we don't have
+        # to look for that package any more.
+        if self.source_pkgs:
+            found = []
+            for pkg in self.source_pkgs:
+                try:
+                    mod = sys.modules[pkg]
+                except KeyError:
+                    continue
+
+                found.append(pkg)
+
+                try:
+                    pkg_file = mod.__file__
+                except AttributeError:
+                    pkg_file = None
+                else:
+                    d, f = os.path.split(pkg_file)
+                    if f.startswith('__init__'):
+                        # This is actually a package, return the directory.
+                        pkg_file = d
+                    else:
+                        pkg_file = self._source_for_file(pkg_file)
+                    pkg_file = self.file_locator.canonical_filename(pkg_file)
+                    if not os.path.exists(pkg_file):
+                        pkg_file = None
+
+                if pkg_file:
+                    self.source.append(pkg_file)
+                    self.source_match.add(pkg_file)
+                else:
+                    self._warn("Module %s has no Python source." % pkg)
+
+            for pkg in found:
+                self.source_pkgs.remove(pkg)
+
+    def use_cache(self, usecache):
+        """Control the use of a data file (incorrectly called a cache).
+
+        `usecache` is true or false, whether to read and write data on disk.
+
+        """
+        self.data.usefile(usecache)
+
+    def load(self):
+        """Load previously-collected coverage data from the data file."""
+        self.collector.reset()
+        self.data.read()
+
+    def start(self):
+        """Start measuring code coverage.
+
+        Coverage measurement actually occurs in functions called after `start`
+        is invoked.  Statements in the same scope as `start` won't be measured.
+
+        Once you invoke `start`, you must also call `stop` eventually, or your
+        process might not shut down cleanly.
+
+        """
+        if self.run_suffix:
+            # Calling start() means we're running code, so use the run_suffix
+            # as the data_suffix when we eventually save the data.
+            self.data_suffix = self.run_suffix
+        if self.auto_data:
+            self.load()
+
+        # Create the matchers we need for _should_trace
+        if self.source or self.source_pkgs:
+            self.source_match = TreeMatcher(self.source)
+        else:
+            if self.cover_dir:
+                self.cover_match = TreeMatcher([self.cover_dir])
+            if self.pylib_dirs:
+                self.pylib_match = TreeMatcher(self.pylib_dirs)
+        if self.include:
+            self.include_match = FnmatchMatcher(self.include)
+        if self.omit:
+            self.omit_match = FnmatchMatcher(self.omit)
+
+        self.collector.start()
+        self._started = True
+        self._measured = True
+
+    def stop(self):
+        """Stop measuring code coverage."""
+        self._started = False
+        self.collector.stop()
+
+    def _atexit(self):
+        """Clean up on process shutdown."""
+        if self._started:
+            self.stop()
+        if self.auto_data:
+            self.save()
+
+    def erase(self):
+        """Erase previously-collected coverage data.
+
+        This removes the in-memory data collected in this session as well as
+        discarding the data file.
+
+        """
+        self.collector.reset()
+        self.data.erase()
+
+    def clear_exclude(self, which='exclude'):
+        """Clear the exclude list."""
+        setattr(self.config, which + "_list", [])
+        self._exclude_regex_stale()
+
+    def exclude(self, regex, which='exclude'):
+        """Exclude source lines from execution consideration.
+
+        A number of lists of regular expressions are maintained.  Each list
+        selects lines that are treated differently during reporting.
+
+        `which` determines which list is modified.  The "exclude" list selects
+        lines that are not considered executable at all.  The "partial" list
+        indicates lines with branches that are not taken.
+
+        `regex` is a regular expression.  The regex is added to the specified
+        list.  If any of the regexes in the list is found in a line, the line
+        is marked for special treatment during reporting.
+
+        """
+        excl_list = getattr(self.config, which + "_list")
+        excl_list.append(regex)
+        self._exclude_regex_stale()
+
+    def _exclude_regex_stale(self):
+        """Drop all the compiled exclusion regexes, a list was modified."""
+        self._exclude_re.clear()
+
+    def _exclude_regex(self, which):
+        """Return a compiled regex for the given exclusion list."""
+        if which not in self._exclude_re:
+            excl_list = getattr(self.config, which + "_list")
+            self._exclude_re[which] = join_regex(excl_list)
+        return self._exclude_re[which]
+
+    def get_exclude_list(self, which='exclude'):
+        """Return a list of excluded regex patterns.
+
+        `which` indicates which list is desired.  See `exclude` for the lists
+        that are available, and their meaning.
+
+        """
+        return getattr(self.config, which + "_list")
+
+    def save(self):
+        """Save the collected coverage data to the data file."""
+        data_suffix = self.data_suffix
+        if data_suffix is True:
+            # If data_suffix was a simple true value, then make a suffix with
+            # plenty of distinguishing information.  We do this here in
+            # `save()` at the last minute so that the pid will be correct even
+            # if the process forks.
+            extra = ""
+            if _TEST_NAME_FILE:
+                f = open(_TEST_NAME_FILE)
+                test_name = f.read()
+                f.close()
+                extra = "." + test_name
+            data_suffix = "%s%s.%s.%06d" % (
+                socket.gethostname(), extra, os.getpid(),
+                random.randint(0, 999999)
+                )
+
+        self._harvest_data()
+        self.data.write(suffix=data_suffix)
+
+    def combine(self):
+        """Combine together a number of similarly-named coverage data files.
+
+        All coverage data files whose name starts with `data_file` (from the
+        coverage() constructor) will be read, and combined together into the
+        current measurements.
+
+        """
+        aliases = None
+        if self.config.paths:
+            aliases = PathAliases(self.file_locator)
+            for paths in self.config.paths.values():
+                result = paths[0]
+                for pattern in paths[1:]:
+                    aliases.add(pattern, result)
+        self.data.combine_parallel_data(aliases=aliases)
+
+    def _harvest_data(self):
+        """Get the collected data and reset the collector.
+
+        Also warn about various problems collecting data.
+
+        """
+        if self._measured:
+            self.data.add_line_data(self.collector.get_line_data())
+            self.data.add_arc_data(self.collector.get_arc_data())
+            self.collector.reset()
+
+            # If there are still entries in the source_pkgs list, then we never
+            # encountered those packages.
+            if self._warn_unimported_source:
+                for pkg in self.source_pkgs:
+                    self._warn("Module %s was never imported." % pkg)
+
+            # Find out if we got any data.
+            summary = self.data.summary()
+            if not summary and self._warn_no_data:
+                self._warn("No data was collected.")
+
+            # Find files that were never executed at all.
+            for src in self.source:
+                for py_file in find_python_files(src):
+                    py_file = self.file_locator.canonical_filename(py_file)
+                    self.data.touch_file(py_file)
+
+            self._measured = False
+
+    # Backward compatibility with version 1.
+    def analysis(self, morf):
+        """Like `analysis2` but doesn't return excluded line numbers."""
+        f, s, _, m, mf = self.analysis2(morf)
+        return f, s, m, mf
+
+    def analysis2(self, morf):
+        """Analyze a module.
+
+        `morf` is a module or a filename.  It will be analyzed to determine
+        its coverage statistics.  The return value is a 5-tuple:
+
+        * The filename for the module.
+        * A list of line numbers of executable statements.
+        * A list of line numbers of excluded statements.
+        * A list of line numbers of statements not run (missing from
+          execution).
+        * A readable formatted string of the missing line numbers.
+
+        The analysis uses the source file itself and the current measured
+        coverage data.
+
+        """
+        analysis = self._analyze(morf)
+        return (
+            analysis.filename, analysis.statements, analysis.excluded,
+            analysis.missing, analysis.missing_formatted()
+            )
+
+    def _analyze(self, it):
+        """Analyze a single morf or code unit.
+
+        Returns an `Analysis` object.
+
+        """
+        self._harvest_data()
+        if not isinstance(it, CodeUnit):
+            it = code_unit_factory(it, self.file_locator)[0]
+
+        return Analysis(self, it)
+
+    def report(self, morfs=None, show_missing=True, ignore_errors=None,
+                file=None,                          # pylint: disable=W0622
+                omit=None, include=None
+                ):
+        """Write a summary report to `file`.
+
+        Each module in `morfs` is listed, with counts of statements, executed
+        statements, missing statements, and a list of lines missed.
+
+        `include` is a list of filename patterns.  Modules whose filenames
+        match those patterns will be included in the report. Modules matching
+        `omit` will not be included in the report.
+
+        Returns a float, the total percentage covered.
+
+        """
+        self._harvest_data()
+        self.config.from_args(
+            ignore_errors=ignore_errors, omit=omit, include=include,
+            show_missing=show_missing,
+            )
+        reporter = SummaryReporter(self, self.config)
+        return reporter.report(morfs, outfile=file)
+
+    def annotate(self, morfs=None, directory=None, ignore_errors=None,
+                    omit=None, include=None):
+        """Annotate a list of modules.
+
+        Each module in `morfs` is annotated.  The source is written to a new
+        file, named with a ",cover" suffix, with each line prefixed with a
+        marker to indicate the coverage of the line.  Covered lines have ">",
+        excluded lines have "-", and missing lines have "!".
+
+        See `coverage.report()` for other arguments.
+
+        """
+        self._harvest_data()
+        self.config.from_args(
+            ignore_errors=ignore_errors, omit=omit, include=include
+            )
+        reporter = AnnotateReporter(self, self.config)
+        reporter.report(morfs, directory=directory)
+
+    def html_report(self, morfs=None, directory=None, ignore_errors=None,
+                    omit=None, include=None, extra_css=None, title=None):
+        """Generate an HTML report.
+
+        The HTML is written to `directory`.  The file "index.html" is the
+        overview starting point, with links to more detailed pages for
+        individual modules.
+
+        `extra_css` is a path to a file of other CSS to apply on the page.
+        It will be copied into the HTML directory.
+
+        `title` is a text string (not HTML) to use as the title of the HTML
+        report.
+
+        See `coverage.report()` for other arguments.
+
+        Returns a float, the total percentage covered.
+
+        """
+        self._harvest_data()
+        self.config.from_args(
+            ignore_errors=ignore_errors, omit=omit, include=include,
+            html_dir=directory, extra_css=extra_css, html_title=title,
+            )
+        reporter = HtmlReporter(self, self.config)
+        return reporter.report(morfs)
+
+    def xml_report(self, morfs=None, outfile=None, ignore_errors=None,
+                    omit=None, include=None):
+        """Generate an XML report of coverage results.
+
+        The report is compatible with Cobertura reports.
+
+        Each module in `morfs` is included in the report.  `outfile` is the
+        path to write the file to, "-" will write to stdout.
+
+        See `coverage.report()` for other arguments.
+
+        Returns a float, the total percentage covered.
+
+        """
+        self._harvest_data()
+        self.config.from_args(
+            ignore_errors=ignore_errors, omit=omit, include=include,
+            xml_output=outfile,
+            )
+        file_to_close = None
+        delete_file = False
+        if self.config.xml_output:
+            if self.config.xml_output == '-':
+                outfile = sys.stdout
+            else:
+                outfile = open(self.config.xml_output, "w")
+                file_to_close = outfile
+        try:
+            try:
+                reporter = XmlReporter(self, self.config)
+                return reporter.report(morfs, outfile=outfile)
+            except CoverageException:
+                delete_file = True
+                raise
+        finally:
+            if file_to_close:
+                file_to_close.close()
+                if delete_file:
+                    file_be_gone(self.config.xml_output)
+
+    def sysinfo(self):
+        """Return a list of (key, value) pairs showing internal information."""
+
+        import coverage as covmod
+        import platform, re
+
+        try:
+            implementation = platform.python_implementation()
+        except AttributeError:
+            implementation = "unknown"
+
+        info = [
+            ('version', covmod.__version__),
+            ('coverage', covmod.__file__),
+            ('cover_dir', self.cover_dir),
+            ('pylib_dirs', self.pylib_dirs),
+            ('tracer', self.collector.tracer_name()),
+            ('config_files', self.config.attempted_config_files),
+            ('configs_read', self.config.config_files),
+            ('data_path', self.data.filename),
+            ('python', sys.version.replace('\n', '')),
+            ('platform', platform.platform()),
+            ('implementation', implementation),
+            ('executable', sys.executable),
+            ('cwd', os.getcwd()),
+            ('path', sys.path),
+            ('environment', [
+                ("%s = %s" % (k, v)) for k, v in iitems(os.environ)
+                    if re.search(r"^COV|^PY", k)
+                ]),
+            ]
+        return info
+
+
+def process_startup():
+    """Call this at Python startup to perhaps measure coverage.
+
+    If the environment variable COVERAGE_PROCESS_START is defined, coverage
+    measurement is started.  The value of the variable is the config file
+    to use.
+
+    There are two ways to configure your Python installation to invoke this
+    function when Python starts:
+
+    #. Create or append to sitecustomize.py to add these lines::
+
+        import coverage
+        coverage.process_startup()
+
+    #. Create a .pth file in your Python installation containing::
+
+        import coverage; coverage.process_startup()
+
+    """
+    cps = os.environ.get("COVERAGE_PROCESS_START")
+    if cps:
+        cov = coverage(config_file=cps, auto_data=True)
+        cov.start()
+        cov._warn_no_data = False
+        cov._warn_unimported_source = False
+
+
+# A hack for debugging testing in subprocesses.
+_TEST_NAME_FILE = "" #"/tmp/covtest.txt"
diff --git a/coverage/data.py b/coverage/data.py
new file mode 100644 (file)
index 0000000..c86a77f
--- /dev/null
@@ -0,0 +1,270 @@
+"""Coverage data for Coverage."""
+
+import os
+
+from coverage.backward import iitems, pickle, sorted    # pylint: disable=W0622
+from coverage.files import PathAliases
+from coverage.misc import file_be_gone
+
+
+class CoverageData(object):
+    """Manages collected coverage data, including file storage.
+
+    The data file format is a pickled dict, with these keys:
+
+        * collector: a string identifying the collecting software
+
+        * lines: a dict mapping filenames to sorted lists of line numbers
+          executed:
+            { 'file1': [17,23,45],  'file2': [1,2,3], ... }
+
+        * arcs: a dict mapping filenames to sorted lists of line number pairs:
+            { 'file1': [(17,23), (17,25), (25,26)], ... }
+
+    """
+
+    def __init__(self, basename=None, collector=None):
+        """Create a CoverageData.
+
+        `basename` is the name of the file to use for storing data.
+
+        `collector` is a string describing the coverage measurement software.
+
+        """
+        self.collector = collector or 'unknown'
+
+        self.use_file = True
+
+        # Construct the filename that will be used for data file storage, if we
+        # ever do any file storage.
+        self.filename = basename or ".coverage"
+        self.filename = os.path.abspath(self.filename)
+
+        # A map from canonical Python source file name to a dictionary in
+        # which there's an entry for each line number that has been
+        # executed:
+        #
+        #   {
+        #       'filename1.py': { 12: None, 47: None, ... },
+        #       ...
+        #       }
+        #
+        self.lines = {}
+
+        # A map from canonical Python source file name to a dictionary with an
+        # entry for each pair of line numbers forming an arc:
+        #
+        #   {
+        #       'filename1.py': { (12,14): None, (47,48): None, ... },
+        #       ...
+        #       }
+        #
+        self.arcs = {}
+
+    def usefile(self, use_file=True):
+        """Set whether or not to use a disk file for data."""
+        self.use_file = use_file
+
+    def read(self):
+        """Read coverage data from the coverage data file (if it exists)."""
+        if self.use_file:
+            self.lines, self.arcs = self._read_file(self.filename)
+        else:
+            self.lines, self.arcs = {}, {}
+
+    def write(self, suffix=None):
+        """Write the collected coverage data to a file.
+
+        `suffix` is a suffix to append to the base file name. This can be used
+        for multiple or parallel execution, so that many coverage data files
+        can exist simultaneously.  A dot will be used to join the base name and
+        the suffix.
+
+        """
+        if self.use_file:
+            filename = self.filename
+            if suffix:
+                filename += "." + suffix
+            self.write_file(filename)
+
+    def erase(self):
+        """Erase the data, both in this object, and from its file storage."""
+        if self.use_file:
+            if self.filename:
+                file_be_gone(self.filename)
+        self.lines = {}
+        self.arcs = {}
+
+    def line_data(self):
+        """Return the map from filenames to lists of line numbers executed."""
+        return dict(
+            [(f, sorted(lmap.keys())) for f, lmap in iitems(self.lines)]
+            )
+
+    def arc_data(self):
+        """Return the map from filenames to lists of line number pairs."""
+        return dict(
+            [(f, sorted(amap.keys())) for f, amap in iitems(self.arcs)]
+            )
+
+    def write_file(self, filename):
+        """Write the coverage data to `filename`."""
+
+        # Create the file data.
+        data = {}
+
+        data['lines'] = self.line_data()
+        arcs = self.arc_data()
+        if arcs:
+            data['arcs'] = arcs
+
+        if self.collector:
+            data['collector'] = self.collector
+
+        # Write the pickle to the file.
+        fdata = open(filename, 'wb')
+        try:
+            pickle.dump(data, fdata, 2)
+        finally:
+            fdata.close()
+
+    def read_file(self, filename):
+        """Read the coverage data from `filename`."""
+        self.lines, self.arcs = self._read_file(filename)
+
+    def raw_data(self, filename):
+        """Return the raw pickled data from `filename`."""
+        fdata = open(filename, 'rb')
+        try:
+            data = pickle.load(fdata)
+        finally:
+            fdata.close()
+        return data
+
+    def _read_file(self, filename):
+        """Return the stored coverage data from the given file.
+
+        Returns two values, suitable for assigning to `self.lines` and
+        `self.arcs`.
+
+        """
+        lines = {}
+        arcs = {}
+        try:
+            data = self.raw_data(filename)
+            if isinstance(data, dict):
+                # Unpack the 'lines' item.
+                lines = dict([
+                    (f, dict.fromkeys(linenos, None))
+                        for f, linenos in iitems(data.get('lines', {}))
+                    ])
+                # Unpack the 'arcs' item.
+                arcs = dict([
+                    (f, dict.fromkeys(arcpairs, None))
+                        for f, arcpairs in iitems(data.get('arcs', {}))
+                    ])
+        except Exception:
+            pass
+        return lines, arcs
+
+    def combine_parallel_data(self, aliases=None):
+        """Combine a number of data files together.
+
+        Treat `self.filename` as a file prefix, and combine the data from all
+        of the data files starting with that prefix plus a dot.
+
+        If `aliases` is provided, it's a `PathAliases` object that is used to
+        re-map paths to match the local machine's.
+
+        """
+        aliases = aliases or PathAliases()
+        data_dir, local = os.path.split(self.filename)
+        localdot = local + '.'
+        for f in os.listdir(data_dir or '.'):
+            if f.startswith(localdot):
+                full_path = os.path.join(data_dir, f)
+                new_lines, new_arcs = self._read_file(full_path)
+                for filename, file_data in iitems(new_lines):
+                    filename = aliases.map(filename)
+                    self.lines.setdefault(filename, {}).update(file_data)
+                for filename, file_data in iitems(new_arcs):
+                    filename = aliases.map(filename)
+                    self.arcs.setdefault(filename, {}).update(file_data)
+                if f != local:
+                    os.remove(full_path)
+
+    def add_line_data(self, line_data):
+        """Add executed line data.
+
+        `line_data` is { filename: { lineno: None, ... }, ...}
+
+        """
+        for filename, linenos in iitems(line_data):
+            self.lines.setdefault(filename, {}).update(linenos)
+
+    def add_arc_data(self, arc_data):
+        """Add measured arc data.
+
+        `arc_data` is { filename: { (l1,l2): None, ... }, ...}
+
+        """
+        for filename, arcs in iitems(arc_data):
+            self.arcs.setdefault(filename, {}).update(arcs)
+
+    def touch_file(self, filename):
+        """Ensure that `filename` appears in the data, empty if needed."""
+        self.lines.setdefault(filename, {})
+
+    def measured_files(self):
+        """A list of all files that had been measured."""
+        return list(self.lines.keys())
+
+    def executed_lines(self, filename):
+        """A map containing all the line numbers executed in `filename`.
+
+        If `filename` hasn't been collected at all (because it wasn't executed)
+        then return an empty map.
+
+        """
+        return self.lines.get(filename) or {}
+
+    def executed_arcs(self, filename):
+        """A map containing all the arcs executed in `filename`."""
+        return self.arcs.get(filename) or {}
+
+    def add_to_hash(self, filename, hasher):
+        """Contribute `filename`'s data to the Md5Hash `hasher`."""
+        hasher.update(self.executed_lines(filename))
+        hasher.update(self.executed_arcs(filename))
+
+    def summary(self, fullpath=False):
+        """Return a dict summarizing the coverage data.
+
+        Keys are based on the filenames, and values are the number of executed
+        lines.  If `fullpath` is true, then the keys are the full pathnames of
+        the files, otherwise they are the basenames of the files.
+
+        """
+        summ = {}
+        if fullpath:
+            filename_fn = lambda f: f
+        else:
+            filename_fn = os.path.basename
+        for filename, lines in iitems(self.lines):
+            summ[filename_fn(filename)] = len(lines)
+        return summ
+
+    def has_arcs(self):
+        """Does this data have arcs?"""
+        return bool(self.arcs)
+
+
+if __name__ == '__main__':
+    # Ad-hoc: show the raw data in a data file.
+    import pprint, sys
+    covdata = CoverageData()
+    if sys.argv[1:]:
+        fname = sys.argv[1]
+    else:
+        fname = covdata.filename
+    pprint.pprint(covdata.raw_data(fname))
diff --git a/coverage/execfile.py b/coverage/execfile.py
new file mode 100644 (file)
index 0000000..587c2d3
--- /dev/null
@@ -0,0 +1,138 @@
+"""Execute files of Python code."""
+
+import imp, os, sys
+
+from coverage.backward import exec_code_object, open_source
+from coverage.misc import NoSource, ExceptionDuringRun
+
+
+try:
+    # In Py 2.x, the builtins were in __builtin__
+    BUILTINS = sys.modules['__builtin__']
+except KeyError:
+    # In Py 3.x, they're in builtins
+    BUILTINS = sys.modules['builtins']
+
+
+def rsplit1(s, sep):
+    """The same as s.rsplit(sep, 1), but works in 2.3"""
+    parts = s.split(sep)
+    return sep.join(parts[:-1]), parts[-1]
+
+
+def run_python_module(modulename, args):
+    """Run a python module, as though with ``python -m name args...``.
+
+    `modulename` is the name of the module, possibly a dot-separated name.
+    `args` is the argument array to present as sys.argv, including the first
+    element naming the module being executed.
+
+    """
+    openfile = None
+    glo, loc = globals(), locals()
+    try:
+        try:
+            # Search for the module - inside its parent package, if any - using
+            # standard import mechanics.
+            if '.' in modulename:
+                packagename, name = rsplit1(modulename, '.')
+                package = __import__(packagename, glo, loc, ['__path__'])
+                searchpath = package.__path__
+            else:
+                packagename, name = None, modulename
+                searchpath = None  # "top-level search" in imp.find_module()
+            openfile, pathname, _ = imp.find_module(name, searchpath)
+
+            # Complain if this is a magic non-file module.
+            if openfile is None and pathname is None:
+                raise NoSource(
+                    "module does not live in a file: %r" % modulename
+                    )
+
+            # If `modulename` is actually a package, not a mere module, then we
+            # pretend to be Python 2.7 and try running its __main__.py script.
+            if openfile is None:
+                packagename = modulename
+                name = '__main__'
+                package = __import__(packagename, glo, loc, ['__path__'])
+                searchpath = package.__path__
+                openfile, pathname, _ = imp.find_module(name, searchpath)
+        except ImportError:
+            _, err, _ = sys.exc_info()
+            raise NoSource(str(err))
+    finally:
+        if openfile:
+            openfile.close()
+
+    # Finally, hand the file off to run_python_file for execution.
+    args[0] = pathname
+    run_python_file(pathname, args, package=packagename)
+
+
+def run_python_file(filename, args, package=None):
+    """Run a python file as if it were the main program on the command line.
+
+    `filename` is the path to the file to execute, it need not be a .py file.
+    `args` is the argument array to present as sys.argv, including the first
+    element naming the file being executed.  `package` is the name of the
+    enclosing package, if any.
+
+    """
+    # Create a module to serve as __main__
+    old_main_mod = sys.modules['__main__']
+    main_mod = imp.new_module('__main__')
+    sys.modules['__main__'] = main_mod
+    main_mod.__file__ = filename
+    if package:
+        main_mod.__package__ = package
+    main_mod.__builtins__ = BUILTINS
+
+    # Set sys.argv and the first path element properly.
+    old_argv = sys.argv
+    old_path0 = sys.path[0]
+    sys.argv = args
+    if package:
+        sys.path[0] = ''
+    else:
+        sys.path[0] = os.path.abspath(os.path.dirname(filename))
+
+    try:
+        # Open the source file.
+        try:
+            source_file = open_source(filename)
+        except IOError:
+            raise NoSource("No file to run: %r" % filename)
+
+        try:
+            source = source_file.read()
+        finally:
+            source_file.close()
+
+        # We have the source.  `compile` still needs the last line to be clean,
+        # so make sure it is, then compile a code object from it.
+        if not source or source[-1] != '\n':
+            source += '\n'
+        code = compile(source, filename, "exec")
+
+        # Execute the source file.
+        try:
+            exec_code_object(code, main_mod.__dict__)
+        except SystemExit:
+            # The user called sys.exit().  Just pass it along to the upper
+            # layers, where it will be handled.
+            raise
+        except:
+            # Something went wrong while executing the user code.
+            # Get the exc_info, and pack them into an exception that we can
+            # throw up to the outer loop.  We peel two layers off the traceback
+            # so that the coverage.py code doesn't appear in the final printed
+            # traceback.
+            typ, err, tb = sys.exc_info()
+            raise ExceptionDuringRun(typ, err, tb.tb_next.tb_next)
+    finally:
+        # Restore the old __main__
+        sys.modules['__main__'] = old_main_mod
+
+        # Restore the old argv and path
+        sys.argv = old_argv
+        sys.path[0] = old_path0
diff --git a/coverage/files.py b/coverage/files.py
new file mode 100644 (file)
index 0000000..3a3a773
--- /dev/null
@@ -0,0 +1,289 @@
+"""File wrangling."""
+
+from coverage.backward import to_string
+from coverage.misc import CoverageException
+import fnmatch, os, os.path, re, sys
+
+class FileLocator(object):
+    """Understand how filenames work."""
+
+    def __init__(self):
+        # The absolute path to our current directory.
+        self.relative_dir = os.path.normcase(abs_file(os.curdir) + os.sep)
+
+        # Cache of results of calling the canonical_filename() method, to
+        # avoid duplicating work.
+        self.canonical_filename_cache = {}
+
+    def relative_filename(self, filename):
+        """Return the relative form of `filename`.
+
+        The filename will be relative to the current directory when the
+        `FileLocator` was constructed.
+
+        """
+        fnorm = os.path.normcase(filename)
+        if fnorm.startswith(self.relative_dir):
+            filename = filename[len(self.relative_dir):]
+        return filename
+
+    def canonical_filename(self, filename):
+        """Return a canonical filename for `filename`.
+
+        An absolute path with no redundant components and normalized case.
+
+        """
+        if filename not in self.canonical_filename_cache:
+            if not os.path.isabs(filename):
+                for path in [os.curdir] + sys.path:
+                    if path is None:
+                        continue
+                    f = os.path.join(path, filename)
+                    if os.path.exists(f):
+                        filename = f
+                        break
+            cf = abs_file(filename)
+            self.canonical_filename_cache[filename] = cf
+        return self.canonical_filename_cache[filename]
+
+    def get_zip_data(self, filename):
+        """Get data from `filename` if it is a zip file path.
+
+        Returns the string data read from the zip file, or None if no zip file
+        could be found or `filename` isn't in it.  The data returned will be
+        an empty string if the file is empty.
+
+        """
+        import zipimport
+        markers = ['.zip'+os.sep, '.egg'+os.sep]
+        for marker in markers:
+            if marker in filename:
+                parts = filename.split(marker)
+                try:
+                    zi = zipimport.zipimporter(parts[0]+marker[:-1])
+                except zipimport.ZipImportError:
+                    continue
+                try:
+                    data = zi.get_data(parts[1])
+                except IOError:
+                    continue
+                return to_string(data)
+        return None
+
+
+if sys.platform == 'win32':
+
+    def actual_path(path):
+        """Get the actual path of `path`, including the correct case."""
+        if path in actual_path.cache:
+            return actual_path.cache[path]
+
+        head, tail = os.path.split(path)
+        if not tail:
+            actpath = head
+        elif not head:
+            actpath = tail
+        else:
+            head = actual_path(head)
+            if head in actual_path.list_cache:
+                files = actual_path.list_cache[head]
+            else:
+                try:
+                    files = os.listdir(head)
+                except OSError:
+                    files = []
+                actual_path.list_cache[head] = files
+            normtail = os.path.normcase(tail)
+            for f in files:
+                if os.path.normcase(f) == normtail:
+                    tail = f
+                    break
+            actpath = os.path.join(head, tail)
+        actual_path.cache[path] = actpath
+        return actpath
+
+    actual_path.cache = {}
+    actual_path.list_cache = {}
+
+else:
+    def actual_path(filename):
+        """The actual path for non-Windows platforms."""
+        return filename
+
+def abs_file(filename):
+    """Return the absolute normalized form of `filename`."""
+    path = os.path.abspath(os.path.realpath(filename))
+    path = actual_path(path)
+    return path
+
+
+def prep_patterns(patterns):
+    """Prepare the file patterns for use in a `FnmatchMatcher`.
+
+    If a pattern starts with a wildcard, it is used as a pattern
+    as-is.  If it does not start with a wildcard, then it is made
+    absolute with the current directory.
+
+    If `patterns` is None, an empty list is returned.
+
+    """
+    patterns = patterns or []
+    prepped = []
+    for p in patterns or []:
+        if p.startswith("*") or p.startswith("?"):
+            prepped.append(p)
+        else:
+            prepped.append(abs_file(p))
+    return prepped
+
+
+class TreeMatcher(object):
+    """A matcher for files in a tree."""
+    def __init__(self, directories):
+        self.dirs = directories[:]
+
+    def __repr__(self):
+        return "<TreeMatcher %r>" % self.dirs
+
+    def add(self, directory):
+        """Add another directory to the list we match for."""
+        self.dirs.append(directory)
+
+    def match(self, fpath):
+        """Does `fpath` indicate a file in one of our trees?"""
+        for d in self.dirs:
+            if fpath.startswith(d):
+                if fpath == d:
+                    # This is the same file!
+                    return True
+                if fpath[len(d)] == os.sep:
+                    # This is a file in the directory
+                    return True
+        return False
+
+
+class FnmatchMatcher(object):
+    """A matcher for files by filename pattern."""
+    def __init__(self, pats):
+        self.pats = pats[:]
+
+    def __repr__(self):
+        return "<FnmatchMatcher %r>" % self.pats
+
+    def match(self, fpath):
+        """Does `fpath` match one of our filename patterns?"""
+        for pat in self.pats:
+            if fnmatch.fnmatch(fpath, pat):
+                return True
+        return False
+
+
+def sep(s):
+    """Find the path separator used in this string, or os.sep if none."""
+    sep_match = re.search(r"[\\/]", s)
+    if sep_match:
+        the_sep = sep_match.group(0)
+    else:
+        the_sep = os.sep
+    return the_sep
+
+
+class PathAliases(object):
+    """A collection of aliases for paths.
+
+    When combining data files from remote machines, often the paths to source
+    code are different, for example, due to OS differences, or because of
+    serialized checkouts on continuous integration machines.
+
+    A `PathAliases` object tracks a list of pattern/result pairs, and can
+    map a path through those aliases to produce a unified path.
+
+    `locator` is a FileLocator that is used to canonicalize the results.
+
+    """
+    def __init__(self, locator=None):
+        self.aliases = []
+        self.locator = locator
+
+    def add(self, pattern, result):
+        """Add the `pattern`/`result` pair to the list of aliases.
+
+        `pattern` is an `fnmatch`-style pattern.  `result` is a simple
+        string.  When mapping paths, if a path starts with a match against
+        `pattern`, then that match is replaced with `result`.  This models
+        isomorphic source trees being rooted at different places on two
+        different machines.
+
+        `pattern` can't end with a wildcard component, since that would
+        match an entire tree, and not just its root.
+
+        """
+        # The pattern can't end with a wildcard component.
+        pattern = pattern.rstrip(r"\/")
+        if pattern.endswith("*"):
+            raise CoverageException("Pattern must not end with wildcards.")
+        pattern_sep = sep(pattern)
+        pattern += pattern_sep
+
+        # Make a regex from the pattern.  fnmatch always adds a \Z or $ to
+        # match the whole string, which we don't want.
+        regex_pat = fnmatch.translate(pattern).replace(r'\Z(', '(')
+        if regex_pat.endswith("$"):
+            regex_pat = regex_pat[:-1]
+        # We want */a/b.py to match on Windows to, so change slash to match
+        # either separator.
+        regex_pat = regex_pat.replace(r"\/", r"[\\/]")
+        # We want case-insensitive matching, so add that flag.
+        regex = re.compile(r"(?i)" + regex_pat)
+
+        # Normalize the result: it must end with a path separator.
+        result_sep = sep(result)
+        result = result.rstrip(r"\/") + result_sep
+        self.aliases.append((regex, result, pattern_sep, result_sep))
+
+    def map(self, path):
+        """Map `path` through the aliases.
+
+        `path` is checked against all of the patterns.  The first pattern to
+        match is used to replace the root of the path with the result root.
+        Only one pattern is ever used.  If no patterns match, `path` is
+        returned unchanged.
+
+        The separator style in the result is made to match that of the result
+        in the alias.
+
+        """
+        for regex, result, pattern_sep, result_sep in self.aliases:
+            m = regex.match(path)
+            if m:
+                new = path.replace(m.group(0), result)
+                if pattern_sep != result_sep:
+                    new = new.replace(pattern_sep, result_sep)
+                if self.locator:
+                    new = self.locator.canonical_filename(new)
+                return new
+        return path
+
+
+def find_python_files(dirname):
+    """Yield all of the importable Python files in `dirname`, recursively.
+
+    To be importable, the files have to be in a directory with a __init__.py,
+    except for `dirname` itself, which isn't required to have one.  The
+    assumption is that `dirname` was specified directly, so the user knows
+    best, but subdirectories are checked for a __init__.py to be sure we only
+    find the importable files.
+
+    """
+    for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
+        if i > 0 and '__init__.py' not in filenames:
+            # If a directory doesn't have __init__.py, then it isn't
+            # importable and neither are its files
+            del dirnames[:]
+            continue
+        for filename in filenames:
+            # We're only interested in files that look like reasonable Python
+            # files: Must end with .py, and must not have certain funny
+            # characters that probably mean they are editor junk.
+            if re.match(r"^[^.#~!$@%^&*()+=,]+\.py$", filename):
+                yield os.path.join(dirpath, filename)
diff --git a/coverage/fullcoverage/encodings.py b/coverage/fullcoverage/encodings.py
new file mode 100644 (file)
index 0000000..6a258d6
--- /dev/null
@@ -0,0 +1,57 @@
+"""Imposter encodings module that installs a coverage-style tracer.
+
+This is NOT the encodings module; it is an imposter that sets up tracing
+instrumentation and then replaces itself with the real encodings module.
+
+If the directory that holds this file is placed first in the PYTHONPATH when
+using "coverage" to run Python's tests, then this file will become the very
+first module imported by the internals of Python 3.  It installs a
+coverage-compatible trace function that can watch Standard Library modules
+execute from the very earliest stages of Python's own boot process.  This fixes
+a problem with coverage - that it starts too late to trace the coverage of many
+of the most fundamental modules in the Standard Library.
+
+"""
+
+import sys
+
+class FullCoverageTracer(object):
+    def __init__(self):
+        # `traces` is a list of trace events.  Frames are tricky: the same
+        # frame object is used for a whole scope, with new line numbers
+        # written into it.  So in one scope, all the frame objects are the
+        # same object, and will eventually all will point to the last line
+        # executed.  So we keep the line numbers alongside the frames.
+        # The list looks like:
+        #
+        #   traces = [
+        #       ((frame, event, arg), lineno), ...
+        #       ]
+        #
+        self.traces = []
+
+    def fullcoverage_trace(self, *args):
+        frame, event, arg = args
+        self.traces.append((args, frame.f_lineno))
+        return self.fullcoverage_trace
+
+sys.settrace(FullCoverageTracer().fullcoverage_trace)
+
+# In coverage/files.py is actual_filename(), which uses glob.glob.  I don't
+# understand why, but that use of glob borks everything if fullcoverage is in
+# effect.  So here we make an ugly hail-mary pass to switch off glob.glob over
+# there.  This means when using fullcoverage, Windows path names will not be
+# their actual case.
+
+#sys.fullcoverage = True
+
+# Finally, remove our own directory from sys.path; remove ourselves from
+# sys.modules; and re-import "encodings", which will be the real package
+# this time.  Note that the delete from sys.modules dictionary has to
+# happen last, since all of the symbols in this module will become None
+# at that exact moment, including "sys".
+
+parentdir = max(filter(__file__.startswith, sys.path), key=len)
+sys.path.remove(parentdir)
+del sys.modules['encodings']
+import encodings
diff --git a/coverage/html.py b/coverage/html.py
new file mode 100644 (file)
index 0000000..ed8920f
--- /dev/null
@@ -0,0 +1,367 @@
+"""HTML reporting for Coverage."""
+
+import os, re, shutil, sys
+
+import coverage
+from coverage.backward import pickle
+from coverage.misc import CoverageException, Hasher
+from coverage.phystokens import source_token_lines, source_encoding
+from coverage.report import Reporter
+from coverage.results import Numbers
+from coverage.templite import Templite
+
+# Disable pylint msg W0612, because a bunch of variables look unused, but
+# they're accessed in a Templite context via locals().
+# pylint: disable=W0612
+
+def data_filename(fname):
+    """Return the path to a data file of ours."""
+    return os.path.join(os.path.split(__file__)[0], fname)
+
+def data(fname):
+    """Return the contents of a data file of ours."""
+    data_file = open(data_filename(fname))
+    try:
+        return data_file.read()
+    finally:
+        data_file.close()
+
+
+class HtmlReporter(Reporter):
+    """HTML reporting."""
+
+    # These files will be copied from the htmlfiles dir to the output dir.
+    STATIC_FILES = [
+            "style.css",
+            "jquery-1.4.3.min.js",
+            "jquery.hotkeys.js",
+            "jquery.isonscreen.js",
+            "jquery.tablesorter.min.js",
+            "coverage_html.js",
+            "keybd_closed.png",
+            "keybd_open.png",
+            ]
+
+    def __init__(self, cov, config):
+        super(HtmlReporter, self).__init__(cov, config)
+        self.directory = None
+        self.template_globals = {
+            'escape': escape,
+            'title': self.config.html_title,
+            '__url__': coverage.__url__,
+            '__version__': coverage.__version__,
+            }
+        self.source_tmpl = Templite(
+            data("htmlfiles/pyfile.html"), self.template_globals
+            )
+
+        self.coverage = cov
+
+        self.files = []
+        self.arcs = self.coverage.data.has_arcs()
+        self.status = HtmlStatus()
+        self.extra_css = None
+        self.totals = Numbers()
+
+    def report(self, morfs):
+        """Generate an HTML report for `morfs`.
+
+        `morfs` is a list of modules or filenames.
+
+        """
+        assert self.config.html_dir, "must give a directory for html reporting"
+
+        # Read the status data.
+        self.status.read(self.config.html_dir)
+
+        # Check that this run used the same settings as the last run.
+        m = Hasher()
+        m.update(self.config)
+        these_settings = m.digest()
+        if self.status.settings_hash() != these_settings:
+            self.status.reset()
+            self.status.set_settings_hash(these_settings)
+
+        # The user may have extra CSS they want copied.
+        if self.config.extra_css:
+            self.extra_css = os.path.basename(self.config.extra_css)
+
+        # Process all the files.
+        self.report_files(self.html_file, morfs, self.config.html_dir)
+
+        if not self.files:
+            raise CoverageException("No data to report.")
+
+        # Write the index file.
+        self.index_file()
+
+        self.make_local_static_report_files()
+
+        return self.totals.pc_covered
+
+    def make_local_static_report_files(self):
+        """Make local instances of static files for HTML report."""
+        # The files we provide must always be copied.
+        for static in self.STATIC_FILES:
+            shutil.copyfile(
+                data_filename("htmlfiles/" + static),
+                os.path.join(self.directory, static)
+                )
+
+        # The user may have extra CSS they want copied.
+        if self.extra_css:
+            shutil.copyfile(
+                self.config.extra_css,
+                os.path.join(self.directory, self.extra_css)
+                )
+
+    def write_html(self, fname, html):
+        """Write `html` to `fname`, properly encoded."""
+        fout = open(fname, "wb")
+        try:
+            fout.write(html.encode('ascii', 'xmlcharrefreplace'))
+        finally:
+            fout.close()
+
+    def file_hash(self, source, cu):
+        """Compute a hash that changes if the file needs to be re-reported."""
+        m = Hasher()
+        m.update(source)
+        self.coverage.data.add_to_hash(cu.filename, m)
+        return m.digest()
+
+    def html_file(self, cu, analysis):
+        """Generate an HTML file for one source file."""
+        source_file = cu.source_file()
+        try:
+            source = source_file.read()
+        finally:
+            source_file.close()
+
+        # Find out if the file on disk is already correct.
+        flat_rootname = cu.flat_rootname()
+        this_hash = self.file_hash(source, cu)
+        that_hash = self.status.file_hash(flat_rootname)
+        if this_hash == that_hash:
+            # Nothing has changed to require the file to be reported again.
+            self.files.append(self.status.index_info(flat_rootname))
+            return
+
+        self.status.set_file_hash(flat_rootname, this_hash)
+
+        # If need be, determine the encoding of the source file. We use it
+        # later to properly write the HTML.
+        if sys.version_info < (3, 0):
+            encoding = source_encoding(source)
+            # Some UTF8 files have the dreaded UTF8 BOM. If so, junk it.
+            if encoding.startswith("utf-8") and source[:3] == "\xef\xbb\xbf":
+                source = source[3:]
+                encoding = "utf-8"
+
+        # Get the numbers for this file.
+        nums = analysis.numbers
+
+        missing_branch_arcs = analysis.missing_branch_arcs()
+        arcs = self.arcs
+
+        # These classes determine which lines are highlighted by default.
+        c_run = "run hide_run"
+        c_exc = "exc"
+        c_mis = "mis"
+        c_par = "par " + c_run
+
+        lines = []
+
+        for lineno, line in enumerate(source_token_lines(source)):
+            lineno += 1     # 1-based line numbers.
+            # Figure out how to mark this line.
+            line_class = []
+            annotate_html = ""
+            annotate_title = ""
+            if lineno in analysis.statements:
+                line_class.append("stm")
+            if lineno in analysis.excluded:
+                line_class.append(c_exc)
+            elif lineno in analysis.missing:
+                line_class.append(c_mis)
+            elif self.arcs and lineno in missing_branch_arcs:
+                line_class.append(c_par)
+                annlines = []
+                for b in missing_branch_arcs[lineno]:
+                    if b < 0:
+                        annlines.append("exit")
+                    else:
+                        annlines.append(str(b))
+                annotate_html = "&nbsp;&nbsp; ".join(annlines)
+                if len(annlines) > 1:
+                    annotate_title = "no jumps to these line numbers"
+                elif len(annlines) == 1:
+                    annotate_title = "no jump to this line number"
+            elif lineno in analysis.statements:
+                line_class.append(c_run)
+
+            # Build the HTML for the line
+            html = []
+            for tok_type, tok_text in line:
+                if tok_type == "ws":
+                    html.append(escape(tok_text))
+                else:
+                    tok_html = escape(tok_text) or '&nbsp;'
+                    html.append(
+                        "<span class='%s'>%s</span>" % (tok_type, tok_html)
+                        )
+
+            lines.append({
+                'html': ''.join(html),
+                'number': lineno,
+                'class': ' '.join(line_class) or "pln",
+                'annotate': annotate_html,
+                'annotate_title': annotate_title,
+            })
+
+        # Write the HTML page for this file.
+        html_filename = flat_rootname + ".html"
+        html_path = os.path.join(self.directory, html_filename)
+        extra_css = self.extra_css
+
+        html = spaceless(self.source_tmpl.render(locals()))
+        if sys.version_info < (3, 0):
+            html = html.decode(encoding)
+        self.write_html(html_path, html)
+
+        # Save this file's information for the index file.
+        index_info = {
+            'nums': nums,
+            'html_filename': html_filename,
+            'name': cu.name,
+            }
+        self.files.append(index_info)
+        self.status.set_index_info(flat_rootname, index_info)
+
+    def index_file(self):
+        """Write the index.html file for this report."""
+        index_tmpl = Templite(
+            data("htmlfiles/index.html"), self.template_globals
+            )
+
+        files = self.files
+        arcs = self.arcs
+
+        self.totals = totals = sum([f['nums'] for f in files])
+        extra_css = self.extra_css
+
+        html = index_tmpl.render(locals())
+        if sys.version_info < (3, 0):
+            html = html.decode("utf-8")
+        self.write_html(
+            os.path.join(self.directory, "index.html"),
+            html
+            )
+
+        # Write the latest hashes for next time.
+        self.status.write(self.directory)
+
+
+class HtmlStatus(object):
+    """The status information we keep to support incremental reporting."""
+
+    STATUS_FILE = "status.dat"
+    STATUS_FORMAT = 1
+
+    def __init__(self):
+        self.reset()
+
+    def reset(self):
+        """Initialize to empty."""
+        self.settings = ''
+        self.files = {}
+
+    def read(self, directory):
+        """Read the last status in `directory`."""
+        usable = False
+        try:
+            status_file = os.path.join(directory, self.STATUS_FILE)
+            fstatus = open(status_file, "rb")
+            try:
+                status = pickle.load(fstatus)
+            finally:
+                fstatus.close()
+        except (IOError, ValueError):
+            usable = False
+        else:
+            usable = True
+            if status['format'] != self.STATUS_FORMAT:
+                usable = False
+            elif status['version'] != coverage.__version__:
+                usable = False
+
+        if usable:
+            self.files = status['files']
+            self.settings = status['settings']
+        else:
+            self.reset()
+
+    def write(self, directory):
+        """Write the current status to `directory`."""
+        status_file = os.path.join(directory, self.STATUS_FILE)
+        status = {
+            'format': self.STATUS_FORMAT,
+            'version': coverage.__version__,
+            'settings': self.settings,
+            'files': self.files,
+            }
+        fout = open(status_file, "wb")
+        try:
+            pickle.dump(status, fout)
+        finally:
+            fout.close()
+
+    def settings_hash(self):
+        """Get the hash of the coverage.py settings."""
+        return self.settings
+
+    def set_settings_hash(self, settings):
+        """Set the hash of the coverage.py settings."""
+        self.settings = settings
+
+    def file_hash(self, fname):
+        """Get the hash of `fname`'s contents."""
+        return self.files.get(fname, {}).get('hash', '')
+
+    def set_file_hash(self, fname, val):
+        """Set the hash of `fname`'s contents."""
+        self.files.setdefault(fname, {})['hash'] = val
+
+    def index_info(self, fname):
+        """Get the information for index.html for `fname`."""
+        return self.files.get(fname, {}).get('index', {})
+
+    def set_index_info(self, fname, info):
+        """Set the information for index.html for `fname`."""
+        self.files.setdefault(fname, {})['index'] = info
+
+
+# Helpers for templates and generating HTML
+
+def escape(t):
+    """HTML-escape the text in `t`."""
+    return (t
+            # Convert HTML special chars into HTML entities.
+            .replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+            .replace("'", "&#39;").replace('"', "&quot;")
+            # Convert runs of spaces: "......" -> "&nbsp;.&nbsp;.&nbsp;."
+            .replace("  ", "&nbsp; ")
+            # To deal with odd-length runs, convert the final pair of spaces
+            # so that "....." -> "&nbsp;.&nbsp;&nbsp;."
+            .replace("  ", "&nbsp; ")
+        )
+
+def spaceless(html):
+    """Squeeze out some annoying extra space from an HTML string.
+
+    Nicely-formatted templates mean lots of extra space in the result.
+    Get rid of some.
+
+    """
+    html = re.sub(r">\s+<p ", ">\n<p ", html)
+    return html
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
new file mode 100644 (file)
index 0000000..b24006d
--- /dev/null
@@ -0,0 +1,376 @@
+// Coverage.py HTML report browser code.
+/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
+/*global coverage: true, document, window, $ */
+
+coverage = {};
+
+// Find all the elements with shortkey_* class, and use them to assign a shotrtcut key.
+coverage.assign_shortkeys = function () {
+    $("*[class*='shortkey_']").each(function (i, e) {
+        $.each($(e).attr("class").split(" "), function (i, c) {
+            if (/^shortkey_/.test(c)) {
+                $(document).bind('keydown', c.substr(9), function () {
+                    $(e).click();
+                });
+            }
+        });
+    });
+};
+
+// Create the events for the help panel.
+coverage.wire_up_help_panel = function () {
+    $("#keyboard_icon").click(function () {
+        // Show the help panel, and position it so the keyboard icon in the
+        // panel is in the same place as the keyboard icon in the header.
+        $(".help_panel").show();
+        var koff = $("#keyboard_icon").offset();
+        var poff = $("#panel_icon").position();
+        $(".help_panel").offset({
+            top: koff.top-poff.top,
+            left: koff.left-poff.left
+        });
+    });
+    $("#panel_icon").click(function () {
+        $(".help_panel").hide();
+    });
+};
+
+// Loaded on index.html
+coverage.index_ready = function ($) {
+    // Look for a cookie containing previous sort settings:
+    var sort_list = [];
+    var cookie_name = "COVERAGE_INDEX_SORT";
+    var i;
+
+    // This almost makes it worth installing the jQuery cookie plugin:
+    if (document.cookie.indexOf(cookie_name) > -1) {
+        var cookies = document.cookie.split(";");
+        for (i = 0; i < cookies.length; i++) {
+            var parts = cookies[i].split("=");
+
+            if ($.trim(parts[0]) === cookie_name && parts[1]) {
+                sort_list = eval("[[" + parts[1] + "]]");
+                break;
+            }
+        }
+    }
+
+    // Create a new widget which exists only to save and restore
+    // the sort order:
+    $.tablesorter.addWidget({
+        id: "persistentSort",
+
+        // Format is called by the widget before displaying:
+        format: function (table) {
+            if (table.config.sortList.length === 0 && sort_list.length > 0) {
+                // This table hasn't been sorted before - we'll use
+                // our stored settings:
+                $(table).trigger('sorton', [sort_list]);
+            }
+            else {
+                // This is not the first load - something has
+                // already defined sorting so we'll just update
+                // our stored value to match:
+                sort_list = table.config.sortList;
+            }
+        }
+    });
+
+    // Configure our tablesorter to handle the variable number of
+    // columns produced depending on report options:
+    var headers = [];
+    var col_count = $("table.index > thead > tr > th").length;
+
+    headers[0] = { sorter: 'text' };
+    for (i = 1; i < col_count-1; i++) {
+        headers[i] = { sorter: 'digit' };
+    }
+    headers[col_count-1] = { sorter: 'percent' };
+
+    // Enable the table sorter:
+    $("table.index").tablesorter({
+        widgets: ['persistentSort'],
+        headers: headers
+    });
+
+    coverage.assign_shortkeys();
+    coverage.wire_up_help_panel();
+
+    // Watch for page unload events so we can save the final sort settings:
+    $(window).unload(function () {
+        document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/";
+    });
+};
+
+// -- pyfile stuff --
+
+coverage.pyfile_ready = function ($) {
+    // If we're directed to a particular line number, highlight the line.
+    var frag = location.hash;
+    if (frag.length > 2 && frag[1] === 'n') {
+        $(frag).addClass('highlight');
+        coverage.set_sel(parseInt(frag.substr(2), 10));
+    }
+    else {
+        coverage.set_sel(0);
+    }
+
+    $(document)
+        .bind('keydown', 'j', coverage.to_next_chunk_nicely)
+        .bind('keydown', 'k', coverage.to_prev_chunk_nicely)
+        .bind('keydown', '0', coverage.to_top)
+        .bind('keydown', '1', coverage.to_first_chunk)
+        ;
+
+    $(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");});
+    $(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");});
+    $(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");});
+    $(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");});
+
+    coverage.assign_shortkeys();
+    coverage.wire_up_help_panel();
+};
+
+coverage.toggle_lines = function (btn, cls) {
+    btn = $(btn);
+    var hide = "hide_"+cls;
+    if (btn.hasClass(hide)) {
+        $("#source ."+cls).removeClass(hide);
+        btn.removeClass(hide);
+    }
+    else {
+        $("#source ."+cls).addClass(hide);
+        btn.addClass(hide);
+    }
+};
+
+// Return the nth line div.
+coverage.line_elt = function (n) {
+    return $("#t" + n);
+};
+
+// Return the nth line number div.
+coverage.num_elt = function (n) {
+    return $("#n" + n);
+};
+
+// Return the container of all the code.
+coverage.code_container = function () {
+    return $(".linenos");
+};
+
+// Set the selection.  b and e are line numbers.
+coverage.set_sel = function (b, e) {
+    // The first line selected.
+    coverage.sel_begin = b;
+    // The next line not selected.
+    coverage.sel_end = (e === undefined) ? b+1 : e;
+};
+
+coverage.to_top = function () {
+    coverage.set_sel(0, 1);
+    coverage.scroll_window(0);
+};
+
+coverage.to_first_chunk = function () {
+    coverage.set_sel(0, 1);
+    coverage.to_next_chunk();
+};
+
+coverage.is_transparent = function (color) {
+    // Different browsers return different colors for "none".
+    return color === "transparent" || color === "rgba(0, 0, 0, 0)";
+};
+
+coverage.to_next_chunk = function () {
+    var c = coverage;
+
+    // Find the start of the next colored chunk.
+    var probe = c.sel_end;
+    while (true) {
+        var probe_line = c.line_elt(probe);
+        if (probe_line.length === 0) {
+            return;
+        }
+        var color = probe_line.css("background-color");
+        if (!c.is_transparent(color)) {
+            break;
+        }
+        probe++;
+    }
+
+    // There's a next chunk, `probe` points to it.
+    var begin = probe;
+
+    // Find the end of this chunk.
+    var next_color = color;
+    while (next_color === color) {
+        probe++;
+        probe_line = c.line_elt(probe);
+        next_color = probe_line.css("background-color");
+    }
+    c.set_sel(begin, probe);
+    c.show_selection();
+};
+
+coverage.to_prev_chunk = function () {
+    var c = coverage;
+
+    // Find the end of the prev colored chunk.
+    var probe = c.sel_begin-1;
+    var probe_line = c.line_elt(probe);
+    if (probe_line.length === 0) {
+        return;
+    }
+    var color = probe_line.css("background-color");
+    while (probe > 0 && c.is_transparent(color)) {
+        probe--;
+        probe_line = c.line_elt(probe);
+        if (probe_line.length === 0) {
+            return;
+        }
+        color = probe_line.css("background-color");
+    }
+
+    // There's a prev chunk, `probe` points to its last line.
+    var end = probe+1;
+
+    // Find the beginning of this chunk.
+    var prev_color = color;
+    while (prev_color === color) {
+        probe--;
+        probe_line = c.line_elt(probe);
+        prev_color = probe_line.css("background-color");
+    }
+    c.set_sel(probe+1, end);
+    c.show_selection();
+};
+
+// Return the line number of the line nearest pixel position pos
+coverage.line_at_pos = function (pos) {
+    var l1 = coverage.line_elt(1),
+        l2 = coverage.line_elt(2),
+        result;
+    if (l1.length && l2.length) {
+        var l1_top = l1.offset().top,
+            line_height = l2.offset().top - l1_top,
+            nlines = (pos - l1_top) / line_height;
+        if (nlines < 1) {
+            result = 1;
+        }
+        else {
+            result = Math.ceil(nlines);
+        }
+    }
+    else {
+        result = 1;
+    }
+    return result;
+};
+
+// Returns 0, 1, or 2: how many of the two ends of the selection are on
+// the screen right now?
+coverage.selection_ends_on_screen = function () {
+    if (coverage.sel_begin === 0) {
+        return 0;
+    }
+
+    var top = coverage.line_elt(coverage.sel_begin);
+    var next = coverage.line_elt(coverage.sel_end-1);
+
+    return (
+        (top.isOnScreen() ? 1 : 0) +
+        (next.isOnScreen() ? 1 : 0)
+    );
+};
+
+coverage.to_next_chunk_nicely = function () {
+    coverage.finish_scrolling();
+    if (coverage.selection_ends_on_screen() === 0) {
+        // The selection is entirely off the screen: select the top line on
+        // the screen.
+        var win = $(window);
+        coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop()));
+    }
+    coverage.to_next_chunk();
+};
+
+coverage.to_prev_chunk_nicely = function () {
+    coverage.finish_scrolling();
+    if (coverage.selection_ends_on_screen() === 0) {
+        var win = $(window);
+        coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height()));
+    }
+    coverage.to_prev_chunk();
+};
+
+// Select line number lineno, or if it is in a colored chunk, select the
+// entire chunk
+coverage.select_line_or_chunk = function (lineno) {
+    var c = coverage;
+    var probe_line = c.line_elt(lineno);
+    if (probe_line.length === 0) {
+        return;
+    }
+    var the_color = probe_line.css("background-color");
+    if (!c.is_transparent(the_color)) {
+        // The line is in a highlighted chunk.
+        // Search backward for the first line.
+        var probe = lineno;
+        var color = the_color;
+        while (probe > 0 && color === the_color) {
+            probe--;
+            probe_line = c.line_elt(probe);
+            if (probe_line.length === 0) {
+                break;
+            }
+            color = probe_line.css("background-color");
+        }
+        var begin = probe + 1;
+
+        // Search forward for the last line.
+        probe = lineno;
+        color = the_color;
+        while (color === the_color) {
+            probe++;
+            probe_line = c.line_elt(probe);
+            color = probe_line.css("background-color");
+        }
+
+        coverage.set_sel(begin, probe);
+    }
+    else {
+        coverage.set_sel(lineno);
+    }
+};
+
+coverage.show_selection = function () {
+    var c = coverage;
+
+    // Highlight the lines in the chunk
+    c.code_container().find(".highlight").removeClass("highlight");
+    for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) {
+        c.num_elt(probe).addClass("highlight");
+    }
+
+    c.scroll_to_selection();
+};
+
+coverage.scroll_to_selection = function () {
+    // Scroll the page if the chunk isn't fully visible.
+    if (coverage.selection_ends_on_screen() < 2) {
+        // Need to move the page. The html,body trick makes it scroll in all
+        // browsers, got it from http://stackoverflow.com/questions/3042651
+        var top = coverage.line_elt(coverage.sel_begin);
+        var top_pos = parseInt(top.offset().top, 10);
+        coverage.scroll_window(top_pos - 30);
+    }
+};
+
+coverage.scroll_window = function (to_pos) {
+    $("html,body").animate({scrollTop: to_pos}, 200);
+};
+
+coverage.finish_scrolling = function () {
+    $("html,body").stop(true, true);
+};
diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html
new file mode 100644 (file)
index 0000000..c649a83
--- /dev/null
@@ -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'>
+    <title>{{ title|escape }}</title>
+    <link rel='stylesheet' href='style.css' type='text/css'>
+    {% if extra_css %}
+        <link rel='stylesheet' href='{{ extra_css }}' type='text/css'>
+    {% endif %}
+    <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>{{ title|escape }}:
+            <span class='pc_cov'>{{totals.pc_covered_str}}%</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>
+        {% if arcs %}
+        <span class='key'>b</span>
+        <span class='key'>p</span>
+        {% endif %}
+        <span class='key'>c</span> &nbsp; change column sorting
+    </p>
+    </div>
+</div>
+
+<div id='index'>
+    <table class='index'>
+        <thead>
+            {# The title='' attr doesn't work in Safari. #}
+            <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>
+                {% if arcs %}
+                <th class='shortkey_b'>branches</th>
+                <th class='shortkey_p'>partial</th>
+                {% endif %}
+                <th class='right shortkey_c'>coverage</th>
+            </tr>
+        </thead>
+        {# HTML syntax requires thead, tfoot, tbody #}
+        <tfoot>
+            <tr class='total'>
+                <td class='name left'>Total</td>
+                <td>{{totals.n_statements}}</td>
+                <td>{{totals.n_missing}}</td>
+                <td>{{totals.n_excluded}}</td>
+                {% if arcs %}
+                <td>{{totals.n_branches}}</td>
+                <td>{{totals.n_partial_branches}}</td>
+                {% endif %}
+                <td class='right'>{{totals.pc_covered_str}}%</td>
+            </tr>
+        </tfoot>
+        <tbody>
+            {% for file in files %}
+            <tr class='file'>
+                <td class='name left'><a href='{{file.html_filename}}'>{{file.name}}</a></td>
+                <td>{{file.nums.n_statements}}</td>
+                <td>{{file.nums.n_missing}}</td>
+                <td>{{file.nums.n_excluded}}</td>
+                {% if arcs %}
+                <td>{{file.nums.n_branches}}</td>
+                <td>{{file.nums.n_partial_branches}}</td>
+                {% endif %}
+                <td class='right'>{{file.nums.pc_covered_str}}%</td>
+            </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+</div>
+
+<div id='footer'>
+    <div class='content'>
+        <p>
+            <a class='nav' href='{{__url__}}'>coverage.py v{{__version__}}</a>
+        </p>
+    </div>
+</div>
+
+</body>
+</html>
diff --git a/coverage/htmlfiles/jquery-1.4.3.min.js b/coverage/htmlfiles/jquery-1.4.3.min.js
new file mode 100644 (file)
index 0000000..c941a5f
--- /dev/null
@@ -0,0 +1,166 @@
+/*!
+ * jQuery JavaScript Library v1.4.3
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Oct 14 23:10:06 2010 -0400
+ */
+(function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;n<H.length;n++){k=H[n];k.origType.replace(X,
+"")===a.type?f.push(k.selector):H.splice(n--,1)}f=c(a.target).closest(f,a.currentTarget);s=0;for(v=f.length;s<v;s++){B=f[s];for(n=0;n<H.length;n++){k=H[n];if(B.selector===k.selector&&(!D||D.test(k.namespace))){l=B.elem;h=null;if(k.preType==="mouseenter"||k.preType==="mouseleave"){a.type=k.preType;h=c(a.relatedTarget).closest(k.selector)[0]}if(!h||h!==l)e.push({elem:l,handleObj:k,level:B.level})}}}s=0;for(v=e.length;s<v;s++){f=e[s];if(d&&f.level>d)break;a.currentTarget=f.elem;a.data=f.handleObj.data;
+a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b,
+e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}
+function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)?
+e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a,
+1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false,
+q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i=
+[u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i);
+else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ":
+"")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r,
+y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r<y;r++)if((F=arguments[r])!=null)for(I in F){K=i[I];J=F[I];if(i!==J)if(z&&J&&(b.isPlainObject(J)||(fa=b.isArray(J)))){if(fa){fa=false;clone=K&&b.isArray(K)?K:[]}else clone=
+K&&b.isPlainObject(K)?K:{};i[I]=b.extend(z,clone,J)}else if(J!==A)i[I]=J}return i};b.extend({noConflict:function(i){E.$=e;if(i)E.jQuery=d;return b},isReady:false,readyWait:1,ready:function(i){i===true&&b.readyWait--;if(!b.readyWait||i!==true&&!b.isReady){if(!u.body)return setTimeout(b.ready,1);b.isReady=true;if(!(i!==true&&--b.readyWait>0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready,
+1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i==
+null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i);
+if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()===
+r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;F<I;){if(r.apply(i[F++],y)===false)break}else if(K)for(z in i){if(r.call(i[z],z,i[z])===false)break}else for(y=i[0];F<I&&r.call(y,F,y)!==false;y=i[++F]);return i},trim:R?function(i){return i==null?"":R.call(i)}:function(i){return i==null?"":i.toString().replace(l,"").replace(n,"")},makeArray:function(i,r){var y=r||[];if(i!=null){var z=b.type(i);i.length==
+null||z==="string"||z==="function"||z==="regexp"||b.isWindow(i)?P.call(y,i):b.merge(y,i)}return y},inArray:function(i,r){if(r.indexOf)return r.indexOf(i);for(var y=0,z=r.length;y<z;y++)if(r[y]===i)return y;return-1},merge:function(i,r){var y=i.length,z=0;if(typeof r.length==="number")for(var F=r.length;z<F;z++)i[y++]=r[z];else for(;r[z]!==A;)i[y++]=r[z++];i.length=y;return i},grep:function(i,r,y){var z=[],F;y=!!y;for(var I=0,K=i.length;I<K;I++){F=!!r(i[I],I);y!==F&&z.push(i[I])}return z},map:function(i,
+r,y){for(var z=[],F,I=0,K=i.length;I<K;I++){F=r(i[I],I,y);if(F!=null)z[z.length]=F}return z.concat.apply([],z)},guid:1,proxy:function(i,r,y){if(arguments.length===2)if(typeof r==="string"){y=i;i=y[r];r=A}else if(r&&!b.isFunction(r)){y=r;r=A}if(!r&&i)r=function(){return i.apply(y||this,arguments)};if(i)r.guid=i.guid=i.guid||r.guid||b.guid++;return r},access:function(i,r,y,z,F,I){var K=i.length;if(typeof r==="object"){for(var J in r)b.access(i,J,r[J],z,F,y);return i}if(y!==A){z=!I&&z&&b.isFunction(y);
+for(J=0;J<K;J++)F(i[J],r,z?y.call(i[J],J,F(i[J],r)):y,I);return i}return K?F(i[0],r):A},now:function(){return(new Date).getTime()},uaMatch:function(i){i=i.toLowerCase();i=M.exec(i)||g.exec(i)||j.exec(i)||i.indexOf("compatible")<0&&o.exec(i)||[];return{browser:i[1]||"",version:i[2]||"0"}},browser:{}});b.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(i,r){L["[object "+r+"]"]=r.toLowerCase()});m=b.uaMatch(m);if(m.browser){b.browser[m.browser]=true;b.browser.version=
+m.version}if(b.browser.webkit)b.browser.safari=true;if(Q)b.inArray=function(i,r){return Q.call(r,i)};if(!/\s/.test("\u00a0")){l=/^[\s\xA0]+/;n=/[\s\xA0]+$/}f=b(u);if(u.addEventListener)t=function(){u.removeEventListener("DOMContentLoaded",t,false);b.ready()};else if(u.attachEvent)t=function(){if(u.readyState==="complete"){u.detachEvent("onreadystatechange",t);b.ready()}};return E.jQuery=E.$=b}();(function(){c.support={};var a=u.documentElement,b=u.createElement("script"),d=u.createElement("div"),
+e="script"+c.now();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var f=d.getElementsByTagName("*"),h=d.getElementsByTagName("a")[0],k=u.createElement("select"),l=k.appendChild(u.createElement("option"));if(!(!f||!f.length||!h)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(h.getAttribute("style")),
+hrefNormalized:h.getAttribute("href")==="/a",opacity:/^0.55$/.test(h.style.opacity),cssFloat:!!h.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:l.selected,optDisabled:false,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableHiddenOffsets:true};k.disabled=true;c.support.optDisabled=!l.disabled;b.type="text/javascript";try{b.appendChild(u.createTextNode("window."+e+"=1;"))}catch(n){}a.insertBefore(b,
+a.firstChild);if(E[e]){c.support.scriptEval=true;delete E[e]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function s(){c.support.noCloneEvent=false;d.detachEvent("onclick",s)});d.cloneNode(true).fireEvent("onclick")}d=u.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div");
+s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="<div style='width:4px;'></div>";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight===
+0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",
+cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]=
+c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b=
+c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e===
+"string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e||
+[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,
+a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,
+a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1)if(f.className){for(var h=" "+f.className+" ",k=f.className,l=0,n=b.length;l<n;l++)if(h.indexOf(" "+b[l]+" ")<0)k+=" "+b[l];f.className=c.trim(k)}else f.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(n){var s=
+c(this);s.removeClass(a.call(this,n,s.attr("class")))});if(a&&typeof a==="string"||a===A)for(var b=(a||"").split(ga),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1&&f.className)if(a){for(var h=(" "+f.className+" ").replace(qa," "),k=0,l=b.length;k<l;k++)h=h.replace(" "+b[k]+" "," ");f.className=c.trim(h)}else f.className=""}return this},toggleClass:function(a,b){var d=typeof a,e=typeof b==="boolean";if(c.isFunction(a))return this.each(function(f){var h=c(this);h.toggleClass(a.call(this,
+f,h.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var f,h=0,k=c(this),l=b,n=a.split(ga);f=n[h++];){l=e?l:!k.hasClass(f);k[l?"addClass":"removeClass"](f)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(qa," ").indexOf(a)>-1)return true;return false},
+val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h<e;h++){var k=f[h];if(k.selected&&(c.support.optDisabled?!k.disabled:k.getAttribute("disabled")===null)&&(!k.parentNode.disabled||!c.nodeName(k.parentNode,"optgroup"))){a=c(k).val();if(b)return a;d.push(a)}}return d}if(ra.test(b.type)&&
+!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Pa,"")}return A}var l=c.isFunction(a);return this.each(function(n){var s=c(this),v=a;if(this.nodeType===1){if(l)v=a.call(this,n,s.val());if(v==null)v="";else if(typeof v==="number")v+="";else if(c.isArray(v))v=c.map(v,function(D){return D==null?"":D+""});if(c.isArray(v)&&ra.test(this.type))this.checked=c.inArray(s.val(),v)>=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&&
+h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l===
+"function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[];
+if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b||
+typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h<B.length;h++){D=B[h];if(d.guid===D.guid){if(l||s.test(D.namespace)){e==null&&B.splice(h--,1);v.remove&&v.remove.call(a,D)}if(e!=null)break}}if(B.length===0||e!=null&&B.length===1){if(!v.teardown||
+v.teardown.call(a,n)===false)c.removeEvent(a,f,w.handle);delete G[f]}}else for(h=0;h<B.length;h++){D=B[h];if(l||s.test(D.namespace)){c.event.remove(a,v,D.handler,h);B.splice(h--,1)}}}if(c.isEmptyObject(G)){if(b=w.handle)b.elem=null;delete w.events;delete w.handle;if(typeof w==="function")c.removeData(a,H);else c.isEmptyObject(w)&&c.removeData(a)}}}}},trigger:function(a,b,d,e){var f=a.type||a;if(!e){a=typeof a==="object"?a[c.expando]?a:c.extend(c.Event(f),a):c.Event(f);if(f.indexOf("!")>=0){a.type=
+f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===
+false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e;
+d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f<k;f++){var l=d[f];if(b||e.test(l.namespace)){a.handler=l.handler;a.data=
+l.data;a.handleObj=l;l=l.handler.apply(this,h);if(l!==A){a.result=l;if(l===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[c.expando])return a;var b=a;a=c.Event(b);for(var d=this.props.length,e;d;){e=this.props[--d];a[e]=b[e]}if(!a.target)a.target=a.srcElement||u;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=u.documentElement;d=u.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(a.which==null&&(a.charCode!=null||a.keyCode!=null))a.which=a.charCode!=null?a.charCode:a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==A)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,Y(a.origType,a.selector),c.extend({},a,{handler:Ga,guid:a.handler.guid}))},remove:function(a){c.event.remove(this,
+Y(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,d){if(c.isWindow(this))this.onbeforeunload=d},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.removeEvent=u.removeEventListener?function(a,b,d){a.removeEventListener&&a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent&&a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=
+c.now();this[c.expando]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=ba;var a=this.originalEvent;if(a)if(a.preventDefault)a.preventDefault();else a.returnValue=false},stopPropagation:function(){this.isPropagationStopped=ba;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=ba;this.stopPropagation()},isDefaultPrevented:U,isPropagationStopped:U,isImmediatePropagationStopped:U};
+var ta=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},ua=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?ua:ta,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?ua:ta)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(){if(this.nodeName.toLowerCase()!==
+"form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length){a.liveFired=A;return ja("submit",this,arguments)}});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13){a.liveFired=A;return ja("submit",this,arguments)}})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};if(!c.support.changeBubbles){var V,
+va=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired=
+A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type===
+"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]===
+0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h<l;h++)c.event.add(this[h],d,k,e)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault)for(var d in a)this.unbind(d,
+a[d]);else{d=0;for(var e=this.length;d<e;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,e){return this.live(b,d,e,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){var d=c.Event(a);d.preventDefault();d.stopPropagation();c.event.trigger(d,b,this[0]);return d.result}},toggle:function(a){for(var b=arguments,d=
+1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(e){var f=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,f+1);e.preventDefault();return b[f].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var wa={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,e,f,h){var k,l=0,n,s,v=h||this.selector;h=h?this:c(this.context);if(typeof d===
+"object"&&!d.preventDefault){for(k in d)h[b](k,e,d[k],v);return this}if(c.isFunction(e)){f=e;e=A}for(d=(d||"").split(" ");(k=d[l++])!=null;){n=X.exec(k);s="";if(n){s=n[0];k=k.replace(X,"")}if(k==="hover")d.push("mouseenter"+s,"mouseleave"+s);else{n=k;if(k==="focus"||k==="blur"){d.push(wa[k]+s);k+=s}else k=(wa[k]||k)+s;if(b==="live"){s=0;for(var B=h.length;s<B;s++)c.event.add(h[s],"live."+Y(k,v),{data:e,selector:v,handler:f,origType:k,origHandler:f,preType:n})}else h.unbind("live."+Y(k,v),f)}}return this}});
+c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d,e){if(e==null){e=d;d=null}return arguments.length>0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
+(function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p<t;p++){var x=m[p];if(x){x=x[g];for(var C=false;x;){if(x.sizcache===o){C=m[x.sizset];break}if(x.nodeType===1&&!q){x.sizcache=o;x.sizset=p}if(x.nodeName.toLowerCase()===j){C=x;break}x=x[g]}m[p]=C}}}function b(g,j,o,m,p,q){p=0;for(var t=m.length;p<t;p++){var x=m[p];if(x){x=x[g];for(var C=false;x;){if(x.sizcache===o){C=m[x.sizset];break}if(x.nodeType===1){if(!q){x.sizcache=o;x.sizset=p}if(typeof j!=="string"){if(x===j){C=true;break}}else if(l.filter(j,
+[x]).length>0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3];
+break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr,
+t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h=
+k;g.sort(w);if(h)for(var j=1;j<g.length;j++)g[j]===g[j-1]&&g.splice(j--,1)}return g};l.matches=function(g,j){return l(g,null,null,j)};l.matchesSelector=function(g,j){return l(j,null,null,[g]).length>0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p<q;p++){var t=n.order[p],x;if(x=n.leftMatch[t].exec(g)){var C=x[1];x.splice(1,1);if(C.substr(C.length-1)!=="\\"){x[1]=(x[1]||"").replace(/\\/g,"");m=n.find[t](x,j,o);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=j.getElementsByTagName("*"));
+return{set:m,expr:g}};l.filter=function(g,j,o,m){for(var p=g,q=[],t=j,x,C,P=j&&j[0]&&l.isXML(j[0]);g&&j.length;){for(var N in n.filter)if((x=n.leftMatch[N].exec(g))!=null&&x[2]){var R=n.filter[N],Q,L;L=x[1];C=false;x.splice(1,1);if(L.substr(L.length-1)!=="\\"){if(t===q)q=[];if(n.preFilter[N])if(x=n.preFilter[N](x,t,o,q,m,P)){if(x===true)continue}else C=Q=true;if(x)for(var i=0;(L=t[i])!=null;i++)if(L){Q=R(L,x,i,t);var r=m^!!Q;if(o&&Q!=null)if(r)C=true;else t[i]=false;else if(r){q.push(L);C=true}}if(Q!==
+A){o||(t=q);g=g.replace(n.match[N],"");if(!C)return[];break}}}if(g===p)if(C==null)l.error(g);else break;p=g}return t};l.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=l.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
+POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,j){var o=typeof j==="string",m=o&&!/\W/.test(j);o=o&&!m;if(m)j=j.toLowerCase();m=0;for(var p=g.length,q;m<p;m++)if(q=g[m]){for(;(q=q.previousSibling)&&q.nodeType!==1;);g[m]=o||q&&q.nodeName.toLowerCase()===
+j?q||false:q===j}o&&l.filter(j,g,true)},">":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p<q;p++){if(m=g[p]){o=m.parentNode;g[p]=o.nodeName.toLowerCase()===j?o:false}}else{for(;p<q;p++)if(m=g[p])g[p]=o?m.parentNode:m.parentNode===j;o&&l.filter(j,g,true)}},"":function(g,j,o){var m=e++,p=b,q;if(typeof j==="string"&&!/\W/.test(j)){q=j=j.toLowerCase();p=a}p("parentNode",j,m,g,q,o)},"~":function(g,j,o){var m=e++,p=b,q;if(typeof j==="string"&&!/\W/.test(j)){q=
+j=j.toLowerCase();p=a}p("previousSibling",j,m,g,q,o)}},find:{ID:function(g,j,o){if(typeof j.getElementById!=="undefined"&&!o)return(g=j.getElementById(g[1]))&&g.parentNode?[g]:[]},NAME:function(g,j){if(typeof j.getElementsByName!=="undefined"){for(var o=[],m=j.getElementsByName(g[1]),p=0,q=m.length;p<q;p++)m[p].getAttribute("name")===g[1]&&o.push(m[p]);return o.length===0?null:o}},TAG:function(g,j){return j.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,j,o,m,p,q){g=" "+g[1].replace(/\\/g,
+"")+" ";if(q)return g;q=0;for(var t;(t=j[q])!=null;q++)if(t)if(p^(t.className&&(" "+t.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o,
+m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===
+true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===
+g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return j<o[3]-0},gt:function(g,j,o){return j>o[3]-0},nth:function(g,j,o){return o[3]-
+0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o<m;o++)if(j[o]===g)return false;return true}else l.error("Syntax error, unrecognized expression: "+p)},CHILD:function(g,j){var o=j[1],m=g;switch(o){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(o===
+"first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":o=j[2];var p=j[3];if(o===1&&p===0)return true;var q=j[0],t=g.parentNode;if(t&&(t.sizcache!==q||!g.nodeIndex)){var x=0;for(m=t.firstChild;m;m=m.nextSibling)if(m.nodeType===1)m.nodeIndex=++x;t.sizcache=q}m=g.nodeIndex-p;return o===0?m===0:m%o===0&&m/o>=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===
+j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]];
+if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o,
+g);else if(typeof g.length==="number")for(var p=g.length;m<p;m++)o.push(g[m]);else for(;g[m];m++)o.push(g[m]);return o}}var w,G;if(u.documentElement.compareDocumentPosition)w=function(g,j){if(g===j){h=true;return 0}if(!g.compareDocumentPosition||!j.compareDocumentPosition)return g.compareDocumentPosition?-1:1;return g.compareDocumentPosition(j)&4?-1:1};else{w=function(g,j){var o=[],m=[],p=g.parentNode,q=j.parentNode,t=p;if(g===j){h=true;return 0}else if(p===q)return G(g,j);else if(p){if(!q)return 1}else return-1;
+for(;t;){o.unshift(t);t=t.parentNode}for(t=q;t;){m.unshift(t);t=t.parentNode}p=o.length;q=m.length;for(t=0;t<p&&t<q;t++)if(o[t]!==m[t])return G(o[t],m[t]);return t===p?G(g,m[t],-1):G(o[t],j,1)};G=function(g,j,o){if(g===j)return o;for(g=g.nextSibling;g;){if(g===j)return-1;g=g.nextSibling}return 1}}l.getText=function(g){for(var j="",o,m=0;g[m];m++){o=g[m];if(o.nodeType===3||o.nodeType===4)j+=o.nodeValue;else if(o.nodeType!==8)j+=l.getText(o.childNodes)}return j};(function(){var g=u.createElement("div"),
+j="script"+(new Date).getTime();g.innerHTML="<a name='"+j+"'/>";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g);
+o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&&
+function(){var g=l,j=u.createElement("div");j.innerHTML="<p class='TEST'></p>";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o];
+j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===
+0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g,
+j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p<t;p++)l(g,q[p],o);return l.filter(m,o)};c.find=l;c.expr=l.selectors;c.expr[":"]=c.expr.filters;c.unique=l.uniqueSort;c.text=l.getText;c.isXMLDoc=l.isXML;c.contains=l.contains})();var Wa=/Until$/,Xa=/^(?:parents|prevUntil|prevAll)/,Ya=/,/,Ja=/^.[^:#\[\.,]*$/,Za=Array.prototype.slice,$a=c.expr.match.POS;c.fn.extend({find:function(a){for(var b=this.pushStack("",
+"find",a),d=0,e=0,f=this.length;e<f;e++){d=b.length;c.find(a,this[e],b);if(e>0)for(var h=d;h<b.length;h++)for(var k=0;k<d;k++)if(b[k]===b[h]){b.splice(h--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,e=b.length;d<e;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(ka(this,a,false),"not",a)},filter:function(a){return this.pushStack(ka(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,
+b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e<f;e++){l=a[e];k[l]||(k[l]=c.expr.match.POS.test(l)?c(l,b||this.context):l)}for(;h&&h.ownerDocument&&h!==b;){for(l in k){e=k[l];if(e.jquery?e.index(h)>-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e<f;e++)for(h=this[e];h;)if(k?k.index(h)>-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h||
+!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}});
+c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",
+d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}});
+c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g,
+$=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/<tbody/i,bb=/<|&#?\w+;/,Aa=/<(?:script|object|embed|option|style)/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,cb=/\=([^="'>\s]+\/)>/g,O={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],
+area:[1,"<map>","</map>"],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,
+d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},
+unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=
+c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*"));
+c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($,
+"")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1></$2>");try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(e){this.empty().append(a)}}else c.isFunction(a)?
+this.each(function(f){var h=c(this);h.html(a.call(this,f,h.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),e=d.html();d.replaceWith(a.call(this,b,e))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,
+true)},domManip:function(a,b,d){var e,f,h=a[0],k=[],l;if(!c.support.checkClone&&arguments.length===3&&typeof h==="string"&&Ba.test(h))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(h))return this.each(function(s){var v=c(this);a[0]=h.call(this,s,b?v.html():A);v.domManip(a,b,d)});if(this[0]){e=h&&h.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:c.buildFragment(a,this,k);l=e.fragment;if(f=l.childNodes.length===1?l=l.firstChild:
+l.firstChild){b=b&&c.nodeName(f,"tr");f=0;for(var n=this.length;f<n;f++)d.call(b?c.nodeName(this[f],"table")?this[f].getElementsByTagName("tbody")[0]||this[f].appendChild(this[f].ownerDocument.createElement("tbody")):this[f]:this[f],f>0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone||
+!Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h=
+d.length;f<h;f++){var k=(f>0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1></$2>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default,
+s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]==="<table>"&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&&
+c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]?
+c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this;
+return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]||
+h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=
+e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":
+b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b],
+h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+mb=/^(?:select|textarea)/i,nb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ob=/^(?:GET|HEAD|DELETE)$/,Na=/\[\]$/,T=/\=\?(&|$)/,ia=/\?/,pb=/([?&])_=[^&]*/,qb=/^(\w+:)?\/\/([^\/?#]+)/,rb=/%20/g,sb=/#.*$/,Ea=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ea)return Ea.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d=
+b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(k,l){if(l==="success"||l==="notmodified")h.html(f?c("<div>").append(k.responseText.replace(lb,"")).find(f):k.responseText);d&&h.each(d,[k.responseText,l,k])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&
+!this.disabled&&(this.checked||mb.test(this.nodeName)||nb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})},
+getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html",
+script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),k=ob.test(h);b.url=b.url.replace(sb,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ia.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data||
+!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+kb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var l=E[d];E[d]=function(m){f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);if(c.isFunction(l))l(m);else{E[d]=A;try{delete E[d]}catch(p){}}v&&v.removeChild(B)}}if(b.dataType==="script"&&b.cache===null)b.cache=
+false;if(b.cache===false&&h==="GET"){var n=c.now(),s=b.url.replace(pb,"$1_="+n);b.url=s+(s===b.url?(ia.test(b.url)?"&":"?")+"_="+n:"")}if(b.data&&h==="GET")b.url+=(ia.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");n=(n=qb.exec(b.url))&&(n[1]&&n[1]!==location.protocol||n[2]!==location.host);if(b.dataType==="script"&&h==="GET"&&n){var v=u.getElementsByTagName("head")[0]||u.documentElement,B=u.createElement("script");if(b.scriptCharset)B.charset=b.scriptCharset;B.src=
+b.url;if(!d){var D=false;B.onload=B.onreadystatechange=function(){if(!D&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){D=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);B.onload=B.onreadystatechange=null;v&&B.parentNode&&v.removeChild(B)}}}v.insertBefore(B,v.firstChild);return A}var H=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!k||a&&a.contentType)w.setRequestHeader("Content-Type",
+b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}n||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(G){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&&
+c.triggerGlobal(b,"ajaxSend",[w,b]);var M=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){H||c.handleComplete(b,w,e,f);H=true;if(w)w.onreadystatechange=c.noop}else if(!H&&w&&(w.readyState===4||m==="timeout")){H=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d||
+c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&g.call&&g.call(w);M("abort")}}catch(j){}b.async&&b.timeout>0&&setTimeout(function(){w&&!H&&M("timeout")},b.timeout);try{w.send(k||b.data==null?null:b.data)}catch(o){c.handleError(b,w,null,o);c.handleComplete(b,w,e,f)}b.async||M();return w}},param:function(a,b){var d=[],e=function(h,k){k=c.isFunction(k)?k():k;d[d.length]=encodeURIComponent(h)+
+"="+encodeURIComponent(k)};if(b===A)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)ca(f,a[f],b,e);return d.join("&").replace(rb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",[b,a])},handleComplete:function(a,
+b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),e=a.getResponseHeader("Etag");
+if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});if(E.ActiveXObject)c.ajaxSettings.xhr=
+function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var da={},tb=/^(?:toggle|show|hide)$/,ub=/^([+\-]=)?([\d+.\-]+)(.*)$/,aa,na=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",3),a,b,d);else{a=
+0;for(b=this.length;a<b;a++){if(!c.data(this[a],"olddisplay")&&this[a].style.display==="none")this[a].style.display="";this[a].style.display===""&&c.css(this[a],"display")==="none"&&c.data(this[a],"olddisplay",oa(this[a].nodeName))}for(a=0;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b,d){if(a||a===0)return this.animate(S("hide",3),a,b,d);else{a=0;for(b=this.length;a<b;a++){d=c.css(this[a],"display");d!=="none"&&c.data(this[a],"olddisplay",d)}for(a=
+0;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b,d){var e=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||e?this.each(function(){var f=e?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(S("toggle",3),a,b,d);return this},fadeTo:function(a,b,d,e){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d,e)},animate:function(a,b,d,e){var f=c.speed(b,d,e);if(c.isEmptyObject(a))return this.each(f.complete);
+return this[f.queue===false?"each":"queue"](function(){var h=c.extend({},f),k,l=this.nodeType===1,n=l&&c(this).is(":hidden"),s=this;for(k in a){var v=c.camelCase(k);if(k!==v){a[v]=a[k];delete a[k];k=v}if(a[k]==="hide"&&n||a[k]==="show"&&!n)return h.complete.call(this);if(l&&(k==="height"||k==="width")){h.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(c.css(this,"display")==="inline"&&c.css(this,"float")==="none")if(c.support.inlineBlockNeedsLayout)if(oa(this.nodeName)===
+"inline")this.style.display="inline-block";else{this.style.display="inline";this.style.zoom=1}else this.style.display="inline-block"}if(c.isArray(a[k])){(h.specialEasing=h.specialEasing||{})[k]=a[k][1];a[k]=a[k][0]}}if(h.overflow!=null)this.style.overflow="hidden";h.curAnim=c.extend({},a);c.each(a,function(B,D){var H=new c.fx(s,h,B);if(tb.test(D))H[D==="toggle"?n?"show":"hide":D](a);else{var w=ub.exec(D),G=H.cur(true)||0;if(w){var M=parseFloat(w[2]),g=w[3]||"px";if(g!=="px"){c.style(s,B,(M||1)+g);
+G=(M||1)/H.cur(true)*G;c.style(s,B,G+g)}if(w[1])M=(w[1]==="-="?-1:1)*M+G;H.custom(G,M,g)}else H.custom(G,D,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var e=d.length-1;e>=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b,
+d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a*
+Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(h){return f.step(h)}
+this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var f=this;a=c.fx;e.elem=this.elem;if(e()&&c.timers.push(e)&&!aa)aa=setInterval(a.tick,a.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;
+this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(l,n){f.style["overflow"+n]=h.overflow[l]})}this.options.hide&&c(this.elem).hide();if(this.options.hide||
+this.options.show)for(var k in this.options.curAnim)c.style(this.elem,k,this.options.orig[k]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=
+c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},interval:13,stop:function(){clearInterval(aa);aa=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===
+b.elem}).length};var vb=/^t(?:able|d|h)$/i,Fa=/^(?:body|html)$/i;c.fn.offset="getBoundingClientRect"in u.documentElement?function(a){var b=this[0],d;if(a)return this.each(function(k){c.offset.setOffset(this,a,k)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);try{d=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,h=f.documentElement;if(!d||!c.contains(h,b))return d||{top:0,left:0};b=f.body;f=ea(f);return{top:d.top+(f.pageYOffset||c.support.boxModel&&
+h.scrollTop||b.scrollTop)-(h.clientTop||b.clientTop||0),left:d.left+(f.pageXOffset||c.support.boxModel&&h.scrollLeft||b.scrollLeft)-(h.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(s){c.offset.setOffset(this,a,s)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,e=b.ownerDocument,f,h=e.documentElement,k=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;
+for(var l=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==k&&b!==h;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;f=e?e.getComputedStyle(b,null):b.currentStyle;l-=b.scrollTop;n-=b.scrollLeft;if(b===d){l+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&vb.test(b.nodeName))){l+=parseFloat(f.borderTopWidth)||0;n+=parseFloat(f.borderLeftWidth)||0}d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&f.overflow!=="visible"){l+=
+parseFloat(f.borderTopWidth)||0;n+=parseFloat(f.borderLeftWidth)||0}f=f}if(f.position==="relative"||f.position==="static"){l+=k.offsetTop;n+=k.offsetLeft}if(c.offset.supportsFixedPosition&&f.position==="fixed"){l+=Math.max(h.scrollTop,k.scrollTop);n+=Math.max(h.scrollLeft,k.scrollLeft)}return{top:l,left:n}};c.offset={initialize:function(){var a=u.body,b=u.createElement("div"),d,e,f,h=parseFloat(c.css(a,"marginTop"))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",
+height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);d=b.firstChild;e=d.firstChild;f=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=e.offsetTop!==5;this.doesAddBorderForTableAndCells=
+f.offsetTop===5;e.style.position="fixed";e.style.top="20px";this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15;e.style.position=e.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==h;a.removeChild(b);c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.css(a,
+"marginTop"))||0;d+=parseFloat(c.css(a,"marginLeft"))||0}return{top:b,left:d}},setOffset:function(a,b,d){var e=c.css(a,"position");if(e==="static")a.style.position="relative";var f=c(a),h=f.offset(),k=c.css(a,"top"),l=c.css(a,"left"),n=e==="absolute"&&c.inArray("auto",[k,l])>-1;e={};var s={};if(n)s=f.position();k=n?s.top:parseInt(k,10)||0;l=n?s.left:parseInt(l,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+k;if(b.left!=null)e.left=b.left-h.left+l;"using"in b?b.using.call(a,
+e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Fa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||u.body;a&&!Fa.test(a.nodeName)&&
+c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==A)return this.each(function(){if(h=ea(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=ea(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();
+c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(h){var k=c(this);k[d](e.call(this,h,k[d]()))});return c.isWindow(f)?f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b]:f.nodeType===9?Math.max(f.documentElement["client"+
+b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]):e===A?parseFloat(c.css(f,d)):this.css(d,typeof e==="string"?e:e+"px")}})})(window);
diff --git a/coverage/htmlfiles/jquery.hotkeys.js b/coverage/htmlfiles/jquery.hotkeys.js
new file mode 100644 (file)
index 0000000..09b21e0
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * http://github.com/tzuryby/hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+*/
+
+(function(jQuery){
+
+       jQuery.hotkeys = {
+               version: "0.8",
+
+               specialKeys: {
+                       8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+                       20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+                       37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
+                       96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
+                       104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
+                       112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
+                       120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
+               },
+
+               shiftNums: {
+                       "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
+                       "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
+                       ".": ">",  "/": "?",  "\\": "|"
+               }
+       };
+
+       function keyHandler( handleObj ) {
+               // Only care when a possible input has been specified
+               if ( typeof handleObj.data !== "string" ) {
+                       return;
+               }
+
+               var origHandler = handleObj.handler,
+                       keys = handleObj.data.toLowerCase().split(" ");
+
+               handleObj.handler = function( event ) {
+                       // Don't fire in text-accepting inputs that we didn't directly bind to
+                       if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
+                                event.target.type === "text") ) {
+                               return;
+                       }
+
+                       // Keypress represents characters, not special keys
+                       var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
+                               character = String.fromCharCode( event.which ).toLowerCase(),
+                               key, modif = "", possible = {};
+
+                       // check combinations (alt|ctrl|shift+anything)
+                       if ( event.altKey && special !== "alt" ) {
+                               modif += "alt+";
+                       }
+
+                       if ( event.ctrlKey && special !== "ctrl" ) {
+                               modif += "ctrl+";
+                       }
+
+                       // TODO: Need to make sure this works consistently across platforms
+                       if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
+                               modif += "meta+";
+                       }
+
+                       if ( event.shiftKey && special !== "shift" ) {
+                               modif += "shift+";
+                       }
+
+                       if ( special ) {
+                               possible[ modif + special ] = true;
+
+                       } else {
+                               possible[ modif + character ] = true;
+                               possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
+
+                               // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+                               if ( modif === "shift+" ) {
+                                       possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
+                               }
+                       }
+
+                       for ( var i = 0, l = keys.length; i < l; i++ ) {
+                               if ( possible[ keys[i] ] ) {
+                                       return origHandler.apply( this, arguments );
+                               }
+                       }
+               };
+       }
+
+       jQuery.each([ "keydown", "keyup", "keypress" ], function() {
+               jQuery.event.special[ this ] = { add: keyHandler };
+       });
+
+})( jQuery );
diff --git a/coverage/htmlfiles/jquery.isonscreen.js b/coverage/htmlfiles/jquery.isonscreen.js
new file mode 100644 (file)
index 0000000..0182ebd
--- /dev/null
@@ -0,0 +1,53 @@
+/* Copyright (c) 2010
+ * @author Laurence Wheway
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * @version 1.2.0
+ */
+(function($) {
+       jQuery.extend({
+               isOnScreen: function(box, container) {
+                       //ensure numbers come in as intgers (not strings) and remove 'px' is it's there
+                       for(var i in box){box[i] = parseFloat(box[i])};
+                       for(var i in container){container[i] = parseFloat(container[i])};
+
+                       if(!container){
+                               container = {
+                                       left: $(window).scrollLeft(),
+                                       top: $(window).scrollTop(),
+                                       width: $(window).width(),
+                                       height: $(window).height()
+                               }
+                       }
+
+                       if(     box.left+box.width-container.left > 0 &&
+                               box.left < container.width+container.left &&
+                               box.top+box.height-container.top > 0 &&
+                               box.top < container.height+container.top
+                       ) return true;
+                       return false;
+               }
+       })
+
+
+       jQuery.fn.isOnScreen = function (container) {
+               for(var i in container){container[i] = parseFloat(container[i])};
+
+               if(!container){
+                       container = {
+                               left: $(window).scrollLeft(),
+                               top: $(window).scrollTop(),
+                               width: $(window).width(),
+                               height: $(window).height()
+                       }
+               }
+
+               if(     $(this).offset().left+$(this).width()-container.left > 0 &&
+                       $(this).offset().left < container.width+container.left &&
+                       $(this).offset().top+$(this).height()-container.top > 0 &&
+                       $(this).offset().top < container.height+container.top
+               ) return true;
+               return false;
+       }
+})(jQuery);
diff --git a/coverage/htmlfiles/jquery.tablesorter.min.js b/coverage/htmlfiles/jquery.tablesorter.min.js
new file mode 100644 (file)
index 0000000..64c7007
--- /dev/null
@@ -0,0 +1,2 @@
+
+(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,cells[i]);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,node){var l=parsers.length;for(var i=1;i<l;i++){if(parsers[i].is($.trim(getElementText(table.config,node)),table,node)){return parsers[i];}}return parsers[0];}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=table.tBodies[0].rows[i],cols=[];cache.row.push($(c));for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c.cells[j]),table,c.cells[j]));}cols.push(i);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){if(!node)return"";var t="";if(config.textExtraction=="simple"){if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){t=node.childNodes[0].innerHTML;}else{t=node.innerHTML;}}else{if(typeof(config.textExtraction)=="function"){t=config.textExtraction(node);}else{t=$(node).text();}}return t;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){rows.push(r[n[i][checkCell]]);if(!table.config.appender){var o=r[n[i][checkCell]];var l=o.length;for(var j=0;j<l;j++){tableBody[0].appendChild(o[j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false,tableHeadersRows=[];for(var i=0;i<table.tHead.rows.length;i++){tableHeadersRows[i]=0;};$tableHeaders=$("thead th",table);$tableHeaders.each(function(index){this.count=0;this.column=index;this.order=formatSortingOrder(table.config.sortInitialOrder);if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(!this.sortDisabled){$(this).addClass(table.config.cssHeader);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){i=(v.toLowerCase()=="desc")?1:0;}else{i=(v==(0||1))?v:0;}return i;}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(getCachedSortType(table.config.parsers,c)=="text")?((order==0)?"sortText":"sortTextDesc"):((order==0)?"sortNumeric":"sortNumericDesc");var e="e"+i;dynamicExp+="var "+e+" = "+s+"(a["+c+"],b["+c+"]); ";dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function sortText(a,b){return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){var DECIMAL='\\'+config.decimal;var exp='/(^[+]?0('+DECIMAL+'0+)?$)|(^([-+]?[1-9][0-9]*)$)|(^([-+]?((0?|[1-9][0-9]*)'+DECIMAL+'(0*[1-9][0-9]*)))$)|(^[-+]?[1-9]+[0-9]*'+DECIMAL+'0+$)/';return RegExp(exp).test($.trim(s));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9.]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}$("tr:visible",table.tBodies[0]).filter(':even').removeClass(table.config.widgetZebra.css[1]).addClass(table.config.widgetZebra.css[0]).end().filter(':odd').removeClass(table.config.widgetZebra.css[0]).addClass(table.config.widgetZebra.css[1]);if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
\ No newline at end of file
diff --git a/coverage/htmlfiles/keybd_closed.png b/coverage/htmlfiles/keybd_closed.png
new file mode 100644 (file)
index 0000000..f2b0418
Binary files /dev/null and b/coverage/htmlfiles/keybd_closed.png differ
diff --git a/coverage/htmlfiles/keybd_open.png b/coverage/htmlfiles/keybd_open.png
new file mode 100644 (file)
index 0000000..a77961d
Binary files /dev/null and b/coverage/htmlfiles/keybd_open.png differ
diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html
new file mode 100644 (file)
index 0000000..525939f
--- /dev/null
@@ -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'>
+    {# IE8 rounds line-height incorrectly, and adding this emulateIE7 line makes it right! #}
+    {# http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/7684445e-f080-4d8f-8529-132763348e21 #}
+    <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+    <title>Coverage for {{cu.name|escape}}: {{nums.pc_covered_str}}%</title>
+    <link rel='stylesheet' href='style.css' type='text/css'>
+    {% if extra_css %}
+        <link rel='stylesheet' href='{{ extra_css }}' type='text/css'>
+    {% endif %}
+    <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>{{cu.name|escape}}</b> :
+            <span class='pc_cov'>{{nums.pc_covered_str}}%</span>
+        </h1>
+        <img id='keyboard_icon' src='keybd_closed.png'>
+        <h2 class='stats'>
+            {{nums.n_statements}} statements &nbsp;
+            <span class='{{c_run}} shortkey_r button_toggle_run'>{{nums.n_executed}} run</span>
+            <span class='{{c_mis}} shortkey_m button_toggle_mis'>{{nums.n_missing}} missing</span>
+            <span class='{{c_exc}} shortkey_x button_toggle_exc'>{{nums.n_excluded}} excluded</span>
+            {% if arcs %}
+                <span class='{{c_par}} shortkey_p button_toggle_par'>{{nums.n_partial_branches}} partial</span>
+            {% endif %}
+        </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'>
+                {% for line in lines %}
+                    <p id='n{{line.number}}' class='{{line.class}}'><a href='#n{{line.number}}'>{{line.number}}</a></p>
+                {% endfor %}
+            </td>
+            <td class='text' valign='top'>
+                {% for line in lines %}
+                    <p id='t{{line.number}}' class='{{line.class}}'>{% if line.annotate %}<span class='annotate' title='{{line.annotate_title}}'>{{line.annotate}}</span>{% endif %}{{line.html}}<span class='strut'>&nbsp;</span></p>
+                {% endfor %}
+            </td>
+        </tr>
+    </table>
+</div>
+
+<div id='footer'>
+    <div class='content'>
+        <p>
+            <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='{{__url__}}'>coverage.py v{{__version__}}</a>
+        </p>
+    </div>
+</div>
+
+</body>
+</html>
diff --git a/coverage/htmlfiles/style.css b/coverage/htmlfiles/style.css
new file mode 100644 (file)
index 0000000..811c640
--- /dev/null
@@ -0,0 +1,300 @@
+/* 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.33333333em;  /* 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;
+}
+
+.stats span.run {
+    background: #ddffdd;
+}
+.stats span.exc {
+    background: #eeeeee;
+}
+.stats span.mis {
+    background: #ffdddd;
+}
+.stats span.hide_run {
+    background: #eeffee;
+}
+.stats span.hide_exc {
+    background: #f5f5f5;
+}
+.stats span.hide_mis {
+    background: #ffeeee;
+}
+.stats span.par {
+    background: #ffffaa;
+}
+.stats span.hide_par {
+    background: #ffffcc;
+}
+
+/* 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/coverage/misc.py b/coverage/misc.py
new file mode 100644 (file)
index 0000000..ece8023
--- /dev/null
@@ -0,0 +1,159 @@
+"""Miscellaneous stuff for Coverage."""
+
+import errno
+import inspect
+import os
+import sys
+
+from coverage.backward import md5, sorted       # pylint: disable=W0622
+from coverage.backward import string_class, to_bytes
+
+
+def nice_pair(pair):
+    """Make a nice string representation of a pair of numbers.
+
+    If the numbers are equal, just return the number, otherwise return the pair
+    with a dash between them, indicating the range.
+
+    """
+    start, end = pair
+    if start == end:
+        return "%d" % start
+    else:
+        return "%d-%d" % (start, end)
+
+
+def format_lines(statements, lines):
+    """Nicely format a list of line numbers.
+
+    Format a list of line numbers for printing by coalescing groups of lines as
+    long as the lines represent consecutive statements.  This will coalesce
+    even if there are gaps between statements.
+
+    For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
+    `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
+
+    """
+    pairs = []
+    i = 0
+    j = 0
+    start = None
+    while i < len(statements) and j < len(lines):
+        if statements[i] == lines[j]:
+            if start == None:
+                start = lines[j]
+            end = lines[j]
+            j += 1
+        elif start:
+            pairs.append((start, end))
+            start = None
+        i += 1
+    if start:
+        pairs.append((start, end))
+    ret = ', '.join(map(nice_pair, pairs))
+    return ret
+
+
+def short_stack():
+    """Return a string summarizing the call stack."""
+    stack = inspect.stack()[:0:-1]
+    return "\n".join(["%30s : %s @%d" % (t[3],t[1],t[2]) for t in stack])
+
+
+def expensive(fn):
+    """A decorator to cache the result of an expensive operation.
+
+    Only applies to methods with no arguments.
+
+    """
+    attr = "_cache_" + fn.__name__
+    def _wrapped(self):
+        """Inner fn that checks the cache."""
+        if not hasattr(self, attr):
+            setattr(self, attr, fn(self))
+        return getattr(self, attr)
+    return _wrapped
+
+
+def bool_or_none(b):
+    """Return bool(b), but preserve None."""
+    if b is None:
+        return None
+    else:
+        return bool(b)
+
+
+def join_regex(regexes):
+    """Combine a list of regexes into one that matches any of them."""
+    if len(regexes) > 1:
+        return "(" + ")|(".join(regexes) + ")"
+    elif regexes:
+        return regexes[0]
+    else:
+        return ""
+
+
+def file_be_gone(path):
+    """Remove a file, and don't get annoyed if it doesn't exist."""
+    try:
+        os.remove(path)
+    except OSError:
+        _, e, _ = sys.exc_info()
+        if e.errno != errno.ENOENT:
+            raise
+
+
+class Hasher(object):
+    """Hashes Python data into md5."""
+    def __init__(self):
+        self.md5 = md5()
+
+    def update(self, v):
+        """Add `v` to the hash, recursively if needed."""
+        self.md5.update(to_bytes(str(type(v))))
+        if isinstance(v, string_class):
+            self.md5.update(to_bytes(v))
+        elif isinstance(v, (int, float)):
+            self.update(str(v))
+        elif isinstance(v, (tuple, list)):
+            for e in v:
+                self.update(e)
+        elif isinstance(v, dict):
+            keys = v.keys()
+            for k in sorted(keys):
+                self.update(k)
+                self.update(v[k])
+        else:
+            for k in dir(v):
+                if k.startswith('__'):
+                    continue
+                a = getattr(v, k)
+                if inspect.isroutine(a):
+                    continue
+                self.update(k)
+                self.update(a)
+
+    def digest(self):
+        """Retrieve the digest of the hash."""
+        return self.md5.digest()
+
+
+class CoverageException(Exception):
+    """An exception specific to Coverage."""
+    pass
+
+class NoSource(CoverageException):
+    """We couldn't find the source for a module."""
+    pass
+
+class NotPython(CoverageException):
+    """A source file turned out not to be parsable Python."""
+    pass
+
+class ExceptionDuringRun(CoverageException):
+    """An exception happened while running customer code.
+
+    Construct it with three arguments, the values from `sys.exc_info`.
+
+    """
+    pass
diff --git a/coverage/parser.py b/coverage/parser.py
new file mode 100644 (file)
index 0000000..4445505
--- /dev/null
@@ -0,0 +1,662 @@
+"""Code parsing for Coverage."""
+
+import opcode, re, sys, token, tokenize
+
+from coverage.backward import set, sorted, StringIO # pylint: disable=W0622
+from coverage.backward import open_source
+from coverage.bytecode import ByteCodes, CodeObjects
+from coverage.misc import nice_pair, expensive, join_regex
+from coverage.misc import CoverageException, NoSource, NotPython
+
+
+class CodeParser(object):
+    """Parse code to find executable lines, excluded lines, etc."""
+
+    def __init__(self, text=None, filename=None, exclude=None):
+        """
+        Source can be provided as `text`, the text itself, or `filename`, from
+        which the text will be read.  Excluded lines are those that match
+        `exclude`, a regex.
+
+        """
+        assert text or filename, "CodeParser needs either text or filename"
+        self.filename = filename or "<code>"
+        self.text = text
+        if not self.text:
+            try:
+                sourcef = open_source(self.filename)
+                try:
+                    self.text = sourcef.read()
+                finally:
+                    sourcef.close()
+            except IOError:
+                _, err, _ = sys.exc_info()
+                raise NoSource(
+                    "No source for code: '%s': %s" % (self.filename, err)
+                    )
+
+        # Scrap the BOM if it exists.
+        if self.text and ord(self.text[0]) == 0xfeff:
+            self.text = self.text[1:]
+
+        self.exclude = exclude
+
+        self.show_tokens = False
+
+        # The text lines of the parsed code.
+        self.lines = self.text.split('\n')
+
+        # The line numbers of excluded lines of code.
+        self.excluded = set()
+
+        # The line numbers of docstring lines.
+        self.docstrings = set()
+
+        # The line numbers of class definitions.
+        self.classdefs = set()
+
+        # A dict mapping line numbers to (lo,hi) for multi-line statements.
+        self.multiline = {}
+
+        # The line numbers that start statements.
+        self.statement_starts = set()
+
+        # Lazily-created ByteParser
+        self._byte_parser = None
+
+    def _get_byte_parser(self):
+        """Create a ByteParser on demand."""
+        if not self._byte_parser:
+            self._byte_parser = \
+                            ByteParser(text=self.text, filename=self.filename)
+        return self._byte_parser
+    byte_parser = property(_get_byte_parser)
+
+    def lines_matching(self, *regexes):
+        """Find the lines matching one of a list of regexes.
+
+        Returns a set of line numbers, the lines that contain a match for one
+        of the regexes in `regexes`.  The entire line needn't match, just a
+        part of it.
+
+        """
+        regex_c = re.compile(join_regex(regexes))
+        matches = set()
+        for i, ltext in enumerate(self.lines):
+            if regex_c.search(ltext):
+                matches.add(i+1)
+        return matches
+
+    def _raw_parse(self):
+        """Parse the source to find the interesting facts about its lines.
+
+        A handful of member fields are updated.
+
+        """
+        # Find lines which match an exclusion pattern.
+        if self.exclude:
+            self.excluded = self.lines_matching(self.exclude)
+
+        # Tokenize, to find excluded suites, to find docstrings, and to find
+        # multi-line statements.
+        indent = 0
+        exclude_indent = 0
+        excluding = False
+        prev_toktype = token.INDENT
+        first_line = None
+        empty = True
+
+        tokgen = tokenize.generate_tokens(StringIO(self.text).readline)
+        for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen:
+            if self.show_tokens:                # pragma: not covered
+                print("%10s %5s %-20r %r" % (
+                    tokenize.tok_name.get(toktype, toktype),
+                    nice_pair((slineno, elineno)), ttext, ltext
+                    ))
+            if toktype == token.INDENT:
+                indent += 1
+            elif toktype == token.DEDENT:
+                indent -= 1
+            elif toktype == token.NAME and ttext == 'class':
+                # Class definitions look like branches in the byte code, so
+                # we need to exclude them.  The simplest way is to note the
+                # lines with the 'class' keyword.
+                self.classdefs.add(slineno)
+            elif toktype == token.OP and ttext == ':':
+                if not excluding and elineno in self.excluded:
+                    # Start excluding a suite.  We trigger off of the colon
+                    # token so that the #pragma comment will be recognized on
+                    # the same line as the colon.
+                    exclude_indent = indent
+                    excluding = True
+            elif toktype == token.STRING and prev_toktype == token.INDENT:
+                # Strings that are first on an indented line are docstrings.
+                # (a trick from trace.py in the stdlib.) This works for
+                # 99.9999% of cases.  For the rest (!) see:
+                # http://stackoverflow.com/questions/1769332/x/1769794#1769794
+                for i in range(slineno, elineno+1):
+                    self.docstrings.add(i)
+            elif toktype == token.NEWLINE:
+                if first_line is not None and elineno != first_line:
+                    # We're at the end of a line, and we've ended on a
+                    # different line than the first line of the statement,
+                    # so record a multi-line range.
+                    rng = (first_line, elineno)
+                    for l in range(first_line, elineno+1):
+                        self.multiline[l] = rng
+                first_line = None
+
+            if ttext.strip() and toktype != tokenize.COMMENT:
+                # A non-whitespace token.
+                empty = False
+                if first_line is None:
+                    # The token is not whitespace, and is the first in a
+                    # statement.
+                    first_line = slineno
+                    # Check whether to end an excluded suite.
+                    if excluding and indent <= exclude_indent:
+                        excluding = False
+                    if excluding:
+                        self.excluded.add(elineno)
+
+            prev_toktype = toktype
+
+        # Find the starts of the executable statements.
+        if not empty:
+            self.statement_starts.update(self.byte_parser._find_statements())
+
+    def first_line(self, line):
+        """Return the first line number of the statement including `line`."""
+        rng = self.multiline.get(line)
+        if rng:
+            first_line = rng[0]
+        else:
+            first_line = line
+        return first_line
+
+    def first_lines(self, lines, ignore=None):
+        """Map the line numbers in `lines` to the correct first line of the
+        statement.
+
+        Skip any line mentioned in `ignore`.
+
+        Returns a sorted list of the first lines.
+
+        """
+        ignore = ignore or []
+        lset = set()
+        for l in lines:
+            if l in ignore:
+                continue
+            new_l = self.first_line(l)
+            if new_l not in ignore:
+                lset.add(new_l)
+        return sorted(lset)
+
+    def parse_source(self):
+        """Parse source text to find executable lines, excluded lines, etc.
+
+        Return values are 1) a sorted list of executable line numbers, and
+        2) a sorted list of excluded line numbers.
+
+        Reported line numbers are normalized to the first line of multi-line
+        statements.
+
+        """
+        try:
+            self._raw_parse()
+        except (tokenize.TokenError, IndentationError):
+            _, tokerr, _ = sys.exc_info()
+            msg, lineno = tokerr.args
+            raise NotPython(
+                "Couldn't parse '%s' as Python source: '%s' at %s" %
+                    (self.filename, msg, lineno)
+                )
+
+        excluded_lines = self.first_lines(self.excluded)
+        ignore = excluded_lines + list(self.docstrings)
+        lines = self.first_lines(self.statement_starts, ignore)
+
+        return lines, excluded_lines
+
+    def arcs(self):
+        """Get information about the arcs available in the code.
+
+        Returns a sorted list of line number pairs.  Line numbers have been
+        normalized to the first line of multiline statements.
+
+        """
+        all_arcs = []
+        for l1, l2 in self.byte_parser._all_arcs():
+            fl1 = self.first_line(l1)
+            fl2 = self.first_line(l2)
+            if fl1 != fl2:
+                all_arcs.append((fl1, fl2))
+        return sorted(all_arcs)
+    arcs = expensive(arcs)
+
+    def exit_counts(self):
+        """Get a mapping from line numbers to count of exits from that line.
+
+        Excluded lines are excluded.
+
+        """
+        excluded_lines = self.first_lines(self.excluded)
+        exit_counts = {}
+        for l1, l2 in self.arcs():
+            if l1 < 0:
+                # Don't ever report -1 as a line number
+                continue
+            if l1 in excluded_lines:
+                # Don't report excluded lines as line numbers.
+                continue
+            if l2 in excluded_lines:
+                # Arcs to excluded lines shouldn't count.
+                continue
+            if l1 not in exit_counts:
+                exit_counts[l1] = 0
+            exit_counts[l1] += 1
+
+        # Class definitions have one extra exit, so remove one for each:
+        for l in self.classdefs:
+            # Ensure key is there: classdefs can include excluded lines.
+            if l in exit_counts:
+                exit_counts[l] -= 1
+
+        return exit_counts
+    exit_counts = expensive(exit_counts)
+
+
+## Opcodes that guide the ByteParser.
+
+def _opcode(name):
+    """Return the opcode by name from the opcode module."""
+    return opcode.opmap[name]
+
+def _opcode_set(*names):
+    """Return a set of opcodes by the names in `names`."""
+    s = set()
+    for name in names:
+        try:
+            s.add(_opcode(name))
+        except KeyError:
+            pass
+    return s
+
+# Opcodes that leave the code object.
+OPS_CODE_END = _opcode_set('RETURN_VALUE')
+
+# Opcodes that unconditionally end the code chunk.
+OPS_CHUNK_END = _opcode_set(
+    'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'RETURN_VALUE', 'RAISE_VARARGS',
+    'BREAK_LOOP', 'CONTINUE_LOOP',
+    )
+
+# Opcodes that unconditionally begin a new code chunk.  By starting new chunks
+# with unconditional jump instructions, we neatly deal with jumps to jumps
+# properly.
+OPS_CHUNK_BEGIN = _opcode_set('JUMP_ABSOLUTE', 'JUMP_FORWARD')
+
+# Opcodes that push a block on the block stack.
+OPS_PUSH_BLOCK = _opcode_set(
+    'SETUP_LOOP', 'SETUP_EXCEPT', 'SETUP_FINALLY', 'SETUP_WITH'
+    )
+
+# Block types for exception handling.
+OPS_EXCEPT_BLOCKS = _opcode_set('SETUP_EXCEPT', 'SETUP_FINALLY')
+
+# Opcodes that pop a block from the block stack.
+OPS_POP_BLOCK = _opcode_set('POP_BLOCK')
+
+# Opcodes that have a jump destination, but aren't really a jump.
+OPS_NO_JUMP = OPS_PUSH_BLOCK
+
+# Individual opcodes we need below.
+OP_BREAK_LOOP = _opcode('BREAK_LOOP')
+OP_END_FINALLY = _opcode('END_FINALLY')
+OP_COMPARE_OP = _opcode('COMPARE_OP')
+COMPARE_EXCEPTION = 10  # just have to get this const from the code.
+OP_LOAD_CONST = _opcode('LOAD_CONST')
+OP_RETURN_VALUE = _opcode('RETURN_VALUE')
+
+
+class ByteParser(object):
+    """Parse byte codes to understand the structure of code."""
+
+    def __init__(self, code=None, text=None, filename=None):
+        if code:
+            self.code = code
+            self.text = text
+        else:
+            if not text:
+                assert filename, "If no code or text, need a filename"
+                sourcef = open_source(filename)
+                try:
+                    text = sourcef.read()
+                finally:
+                    sourcef.close()
+            self.text = text
+
+            try:
+                # Python 2.3 and 2.4 don't like partial last lines, so be sure
+                # the text ends nicely for them.
+                self.code = compile(text + '\n', filename, "exec")
+            except SyntaxError:
+                _, synerr, _ = sys.exc_info()
+                raise NotPython(
+                    "Couldn't parse '%s' as Python source: '%s' at line %d" %
+                        (filename, synerr.msg, synerr.lineno)
+                    )
+
+        # Alternative Python implementations don't always provide all the
+        # attributes on code objects that we need to do the analysis.
+        for attr in ['co_lnotab', 'co_firstlineno', 'co_consts', 'co_code']:
+            if not hasattr(self.code, attr):
+                raise CoverageException(
+                    "This implementation of Python doesn't support code "
+                    "analysis.\n"
+                    "Run coverage.py under CPython for this command."
+                    )
+
+    def child_parsers(self):
+        """Iterate over all the code objects nested within this one.
+
+        The iteration includes `self` as its first value.
+
+        """
+        children = CodeObjects(self.code)
+        return [ByteParser(code=c, text=self.text) for c in children]
+
+    # Getting numbers from the lnotab value changed in Py3.0.
+    if sys.version_info >= (3, 0):
+        def _lnotab_increments(self, lnotab):
+            """Return a list of ints from the lnotab bytes in 3.x"""
+            return list(lnotab)
+    else:
+        def _lnotab_increments(self, lnotab):
+            """Return a list of ints from the lnotab string in 2.x"""
+            return [ord(c) for c in lnotab]
+
+    def _bytes_lines(self):
+        """Map byte offsets to line numbers in `code`.
+
+        Uses co_lnotab described in Python/compile.c to map byte offsets to
+        line numbers.  Returns a list: [(b0, l0), (b1, l1), ...]
+
+        """
+        # Adapted from dis.py in the standard library.
+        byte_increments = self._lnotab_increments(self.code.co_lnotab[0::2])
+        line_increments = self._lnotab_increments(self.code.co_lnotab[1::2])
+
+        bytes_lines = []
+        last_line_num = None
+        line_num = self.code.co_firstlineno
+        byte_num = 0
+        for byte_incr, line_incr in zip(byte_increments, line_increments):
+            if byte_incr:
+                if line_num != last_line_num:
+                    bytes_lines.append((byte_num, line_num))
+                    last_line_num = line_num
+                byte_num += byte_incr
+            line_num += line_incr
+        if line_num != last_line_num:
+            bytes_lines.append((byte_num, line_num))
+        return bytes_lines
+
+    def _find_statements(self):
+        """Find the statements in `self.code`.
+
+        Return a set of line numbers that start statements.  Recurses into all
+        code objects reachable from `self.code`.
+
+        """
+        stmts = set()
+        for bp in self.child_parsers():
+            # Get all of the lineno information from this code.
+            for _, l in bp._bytes_lines():
+                stmts.add(l)
+        return stmts
+
+    def _split_into_chunks(self):
+        """Split the code object into a list of `Chunk` objects.
+
+        Each chunk is only entered at its first instruction, though there can
+        be many exits from a chunk.
+
+        Returns a list of `Chunk` objects.
+
+        """
+
+        # The list of chunks so far, and the one we're working on.
+        chunks = []
+        chunk = None
+        bytes_lines_map = dict(self._bytes_lines())
+
+        # The block stack: loops and try blocks get pushed here for the
+        # implicit jumps that can occur.
+        # Each entry is a tuple: (block type, destination)
+        block_stack = []
+
+        # Some op codes are followed by branches that should be ignored.  This
+        # is a count of how many ignores are left.
+        ignore_branch = 0
+
+        # We have to handle the last two bytecodes specially.
+        ult = penult = None
+
+        for bc in ByteCodes(self.code.co_code):
+            # Maybe have to start a new chunk
+            if bc.offset in bytes_lines_map:
+                # Start a new chunk for each source line number.
+                if chunk:
+                    chunk.exits.add(bc.offset)
+                chunk = Chunk(bc.offset, bytes_lines_map[bc.offset])
+                chunks.append(chunk)
+            elif bc.op in OPS_CHUNK_BEGIN:
+                # Jumps deserve their own unnumbered chunk.  This fixes
+                # problems with jumps to jumps getting confused.
+                if chunk:
+                    chunk.exits.add(bc.offset)
+                chunk = Chunk(bc.offset)
+                chunks.append(chunk)
+
+            if not chunk:
+                chunk = Chunk(bc.offset)
+                chunks.append(chunk)
+
+            # Look at the opcode
+            if bc.jump_to >= 0 and bc.op not in OPS_NO_JUMP:
+                if ignore_branch:
+                    # Someone earlier wanted us to ignore this branch.
+                    ignore_branch -= 1
+                else:
+                    # The opcode has a jump, it's an exit for this chunk.
+                    chunk.exits.add(bc.jump_to)
+
+            if bc.op in OPS_CODE_END:
+                # The opcode can exit the code object.
+                chunk.exits.add(-self.code.co_firstlineno)
+            if bc.op in OPS_PUSH_BLOCK:
+                # The opcode adds a block to the block_stack.
+                block_stack.append((bc.op, bc.jump_to))
+            if bc.op in OPS_POP_BLOCK:
+                # The opcode pops a block from the block stack.
+                block_stack.pop()
+            if bc.op in OPS_CHUNK_END:
+                # This opcode forces the end of the chunk.
+                if bc.op == OP_BREAK_LOOP:
+                    # A break is implicit: jump where the top of the
+                    # block_stack points.
+                    chunk.exits.add(block_stack[-1][1])
+                chunk = None
+            if bc.op == OP_END_FINALLY:
+                if block_stack:
+                    # A break that goes through a finally will jump to whatever
+                    # block is on top of the stack.
+                    chunk.exits.add(block_stack[-1][1])
+                # For the finally clause we need to find the closest exception
+                # block, and use its jump target as an exit.
+                for iblock in range(len(block_stack)-1, -1, -1):
+                    if block_stack[iblock][0] in OPS_EXCEPT_BLOCKS:
+                        chunk.exits.add(block_stack[iblock][1])
+                        break
+            if bc.op == OP_COMPARE_OP and bc.arg == COMPARE_EXCEPTION:
+                # This is an except clause.  We want to overlook the next
+                # branch, so that except's don't count as branches.
+                ignore_branch += 1
+
+            penult = ult
+            ult = bc
+
+        if chunks:
+            # The last two bytecodes could be a dummy "return None" that
+            # shouldn't be counted as real code. Every Python code object seems
+            # to end with a return, and a "return None" is inserted if there
+            # isn't an explicit return in the source.
+            if ult and penult:
+                if penult.op == OP_LOAD_CONST and ult.op == OP_RETURN_VALUE:
+                    if self.code.co_consts[penult.arg] is None:
+                        # This is "return None", but is it dummy?  A real line
+                        # would be a last chunk all by itself.
+                        if chunks[-1].byte != penult.offset:
+                            ex = -self.code.co_firstlineno
+                            # Split the last chunk
+                            last_chunk = chunks[-1]
+                            last_chunk.exits.remove(ex)
+                            last_chunk.exits.add(penult.offset)
+                            chunk = Chunk(penult.offset)
+                            chunk.exits.add(ex)
+                            chunks.append(chunk)
+
+            # Give all the chunks a length.
+            chunks[-1].length = bc.next_offset - chunks[-1].byte # pylint: disable=W0631,C0301
+            for i in range(len(chunks)-1):
+                chunks[i].length = chunks[i+1].byte - chunks[i].byte
+
+        return chunks
+
+    def _arcs(self):
+        """Find the executable arcs in the code.
+
+        Returns a set of pairs, (from,to).  From and to are integer line
+        numbers.  If from is < 0, then the arc is an entrance into the code
+        object.  If to is < 0, the arc is an exit from the code object.
+
+        """
+        chunks = self._split_into_chunks()
+
+        # A map from byte offsets to chunks jumped into.
+        byte_chunks = dict([(c.byte, c) for c in chunks])
+
+        # Build a map from byte offsets to actual lines reached.
+        byte_lines = {}
+        bytes_to_add = set([c.byte for c in chunks])
+
+        while bytes_to_add:
+            byte_to_add = bytes_to_add.pop()
+            if byte_to_add in byte_lines or byte_to_add < 0:
+                continue
+
+            # Which lines does this chunk lead to?
+            bytes_considered = set()
+            bytes_to_consider = [byte_to_add]
+            lines = set()
+
+            while bytes_to_consider:
+                byte = bytes_to_consider.pop()
+                bytes_considered.add(byte)
+
+                # Find chunk for byte
+                try:
+                    ch = byte_chunks[byte]
+                except KeyError:
+                    for ch in chunks:
+                        if ch.byte <= byte < ch.byte+ch.length:
+                            break
+                    else:
+                        # No chunk for this byte!
+                        raise Exception("Couldn't find chunk @ %d" % byte)
+                    byte_chunks[byte] = ch          # pylint: disable=W0631
+
+                if ch.line:
+                    lines.add(ch.line)
+                else:
+                    for ex in ch.exits:
+                        if ex < 0:
+                            lines.add(ex)
+                        elif ex not in bytes_considered:
+                            bytes_to_consider.append(ex)
+
+                bytes_to_add.update(ch.exits)
+
+            byte_lines[byte_to_add] = lines
+
+        # Figure out for each chunk where the exits go.
+        arcs = set()
+        for chunk in chunks:
+            if chunk.line:
+                for ex in chunk.exits:
+                    if ex < 0:
+                        exit_lines = [ex]
+                    else:
+                        exit_lines = byte_lines[ex]
+                    for exit_line in exit_lines:
+                        if chunk.line != exit_line:
+                            arcs.add((chunk.line, exit_line))
+        for line in byte_lines[0]:
+            arcs.add((-1, line))
+
+        return arcs
+
+    def _all_chunks(self):
+        """Returns a list of `Chunk` objects for this code and its children.
+
+        See `_split_into_chunks` for details.
+
+        """
+        chunks = []
+        for bp in self.child_parsers():
+            chunks.extend(bp._split_into_chunks())
+
+        return chunks
+
+    def _all_arcs(self):
+        """Get the set of all arcs in this code object and its children.
+
+        See `_arcs` for details.
+
+        """
+        arcs = set()
+        for bp in self.child_parsers():
+            arcs.update(bp._arcs())
+
+        return arcs
+
+
+class Chunk(object):
+    """A sequence of bytecodes with a single entrance.
+
+    To analyze byte code, we have to divide it into chunks, sequences of byte
+    codes such that each basic block has only one entrance, the first
+    instruction in the block.
+
+    This is almost the CS concept of `basic block`_, except that we're willing
+    to have many exits from a chunk, and "basic block" is a more cumbersome
+    term.
+
+    .. _basic block: http://en.wikipedia.org/wiki/Basic_block
+
+    An exit < 0 means the chunk can leave the code (return).  The exit is
+    the negative of the starting line number of the code block.
+
+    """
+    def __init__(self, byte, line=0):
+        self.byte = byte
+        self.line = line
+        self.length = 0
+        self.exits = set()
+
+    def __repr__(self):
+        return "<%d+%d @%d %r>" % (
+            self.byte, self.length, self.line, list(self.exits)
+            )
diff --git a/coverage/phystokens.py b/coverage/phystokens.py
new file mode 100644 (file)
index 0000000..166020e
--- /dev/null
@@ -0,0 +1,206 @@
+"""Better tokenizing for coverage.py."""
+
+import codecs, keyword, re, sys, token, tokenize
+from coverage.backward import StringIO              # pylint: disable=W0622
+
+def phys_tokens(toks):
+    """Return all physical tokens, even line continuations.
+
+    tokenize.generate_tokens() doesn't return a token for the backslash that
+    continues lines.  This wrapper provides those tokens so that we can
+    re-create a faithful representation of the original source.
+
+    Returns the same values as generate_tokens()
+
+    """
+    last_line = None
+    last_lineno = -1
+    last_ttype = None
+    for ttype, ttext, (slineno, scol), (elineno, ecol), ltext in toks:
+        if last_lineno != elineno:
+            if last_line and last_line[-2:] == "\\\n":
+                # We are at the beginning of a new line, and the last line
+                # ended with a backslash.  We probably have to inject a
+                # backslash token into the stream. Unfortunately, there's more
+                # to figure out.  This code::
+                #
+                #   usage = """\
+                #   HEY THERE
+                #   """
+                #
+                # triggers this condition, but the token text is::
+                #
+                #   '"""\\\nHEY THERE\n"""'
+                #
+                # so we need to figure out if the backslash is already in the
+                # string token or not.
+                inject_backslash = True
+                if last_ttype == tokenize.COMMENT:
+                    # Comments like this \
+                    # should never result in a new token.
+                    inject_backslash = False
+                elif ttype == token.STRING:
+                    if "\n" in ttext and ttext.split('\n', 1)[0][-1] == '\\':
+                        # It's a multiline string and the first line ends with
+                        # a backslash, so we don't need to inject another.
+                        inject_backslash = False
+                if inject_backslash:
+                    # Figure out what column the backslash is in.
+                    ccol = len(last_line.split("\n")[-2]) - 1
+                    # Yield the token, with a fake token type.
+                    yield (
+                        99999, "\\\n",
+                        (slineno, ccol), (slineno, ccol+2),
+                        last_line
+                        )
+            last_line = ltext
+            last_ttype = ttype
+        yield ttype, ttext, (slineno, scol), (elineno, ecol), ltext
+        last_lineno = elineno
+
+
+def source_token_lines(source):
+    """Generate a series of lines, one for each line in `source`.
+
+    Each line is a list of pairs, each pair is a token::
+
+        [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
+
+    Each pair has a token class, and the token text.
+
+    If you concatenate all the token texts, and then join them with newlines,
+    you should have your original `source` back, with two differences:
+    trailing whitespace is not preserved, and a final line with no newline
+    is indistinguishable from a final line with a newline.
+
+    """
+    ws_tokens = [token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL]
+    line = []
+    col = 0
+    source = source.expandtabs(8).replace('\r\n', '\n')
+    tokgen = tokenize.generate_tokens(StringIO(source).readline)
+    for ttype, ttext, (_, scol), (_, ecol), _ in phys_tokens(tokgen):
+        mark_start = True
+        for part in re.split('(\n)', ttext):
+            if part == '\n':
+                yield line
+                line = []
+                col = 0
+                mark_end = False
+            elif part == '':
+                mark_end = False
+            elif ttype in ws_tokens:
+                mark_end = False
+            else:
+                if mark_start and scol > col:
+                    line.append(("ws", " " * (scol - col)))
+                    mark_start = False
+                tok_class = tokenize.tok_name.get(ttype, 'xx').lower()[:3]
+                if ttype == token.NAME and keyword.iskeyword(ttext):
+                    tok_class = "key"
+                line.append((tok_class, part))
+                mark_end = True
+            scol = 0
+        if mark_end:
+            col = ecol
+
+    if line:
+        yield line
+
+def source_encoding(source):
+    """Determine the encoding for `source` (a string), according to PEP 263.
+
+    Returns a string, the name of the encoding.
+
+    """
+    # Note: this function should never be called on Python 3, since py3 has
+    # built-in tools to do this.
+    assert sys.version_info < (3, 0)
+
+    # This is mostly code adapted from Py3.2's tokenize module.
+
+    cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)")
+
+    # Do this so the detect_encode code we copied will work.
+    readline = iter(source.splitlines()).next
+
+    def _get_normal_name(orig_enc):
+        """Imitates get_normal_name in tokenizer.c."""
+        # Only care about the first 12 characters.
+        enc = orig_enc[:12].lower().replace("_", "-")
+        if re.match(r"^utf-8($|-)", enc):
+            return "utf-8"
+        if re.match(r"^(latin-1|iso-8859-1|iso-latin-1)($|-)", enc):
+            return "iso-8859-1"
+        return orig_enc
+
+    # From detect_encode():
+    # It detects the encoding from the presence of a utf-8 bom or an encoding
+    # cookie as specified in pep-0263.  If both a bom and a cookie are present,
+    # but disagree, a SyntaxError will be raised.  If the encoding cookie is an
+    # invalid charset, raise a SyntaxError.  Note that if a utf-8 bom is found,
+    # 'utf-8-sig' is returned.
+
+    # If no encoding is specified, then the default will be returned.  The
+    # default varied with version.
+
+    if sys.version_info <= (2, 4):
+        default = 'iso-8859-1'
+    else:
+        default = 'ascii'
+
+    bom_found = False
+    encoding = None
+
+    def read_or_stop():
+        """Get the next source line, or ''."""
+        try:
+            return readline()
+        except StopIteration:
+            return ''
+
+    def find_cookie(line):
+        """Find an encoding cookie in `line`."""
+        try:
+            line_string = line.decode('ascii')
+        except UnicodeDecodeError:
+            return None
+
+        matches = cookie_re.findall(line_string)
+        if not matches:
+            return None
+        encoding = _get_normal_name(matches[0])
+        try:
+            codec = codecs.lookup(encoding)
+        except LookupError:
+            # This behaviour mimics the Python interpreter
+            raise SyntaxError("unknown encoding: " + encoding)
+
+        if bom_found:
+            if codec.name != 'utf-8':
+                # This behaviour mimics the Python interpreter
+                raise SyntaxError('encoding problem: utf-8')
+            encoding += '-sig'
+        return encoding
+
+    first = read_or_stop()
+    if first.startswith(codecs.BOM_UTF8):
+        bom_found = True
+        first = first[3:]
+        default = 'utf-8-sig'
+    if not first:
+        return default
+
+    encoding = find_cookie(first)
+    if encoding:
+        return encoding
+
+    second = read_or_stop()
+    if not second:
+        return default
+
+    encoding = find_cookie(second)
+    if encoding:
+        return encoding
+
+    return default
diff --git a/coverage/report.py b/coverage/report.py
new file mode 100644 (file)
index 0000000..34f4442
--- /dev/null
@@ -0,0 +1,92 @@
+"""Reporter foundation for Coverage."""
+
+import fnmatch, os
+from coverage.codeunit import code_unit_factory
+from coverage.files import prep_patterns
+from coverage.misc import CoverageException, NoSource, NotPython
+
+class Reporter(object):
+    """A base class for all reporters."""
+
+    def __init__(self, coverage, config):
+        """Create a reporter.
+
+        `coverage` is the coverage instance. `config` is an instance  of
+        CoverageConfig, for controlling all sorts of behavior.
+
+        """
+        self.coverage = coverage
+        self.config = config
+
+        # The code units to report on.  Set by find_code_units.
+        self.code_units = []
+
+        # The directory into which to place the report, used by some derived
+        # classes.
+        self.directory = None
+
+    def find_code_units(self, morfs):
+        """Find the code units we'll report on.
+
+        `morfs` is a list of modules or filenames.
+
+        """
+        morfs = morfs or self.coverage.data.measured_files()
+        file_locator = self.coverage.file_locator
+        self.code_units = code_unit_factory(morfs, file_locator)
+
+        if self.config.include:
+            patterns = prep_patterns(self.config.include)
+            filtered = []
+            for cu in self.code_units:
+                for pattern in patterns:
+                    if fnmatch.fnmatch(cu.filename, pattern):
+                        filtered.append(cu)
+                        break
+            self.code_units = filtered
+
+        if self.config.omit:
+            patterns = prep_patterns(self.config.omit)
+            filtered = []
+            for cu in self.code_units:
+                for pattern in patterns:
+                    if fnmatch.fnmatch(cu.filename, pattern):
+                        break
+                else:
+                    filtered.append(cu)
+            self.code_units = filtered
+
+        self.code_units.sort()
+
+    def report_files(self, report_fn, morfs, directory=None):
+        """Run a reporting function on a number of morfs.
+
+        `report_fn` is called for each relative morf in `morfs`.  It is called
+        as::
+
+            report_fn(code_unit, analysis)
+
+        where `code_unit` is the `CodeUnit` for the morf, and `analysis` is
+        the `Analysis` for the morf.
+
+        """
+        self.find_code_units(morfs)
+
+        if not self.code_units:
+            raise CoverageException("No data to report.")
+
+        self.directory = directory
+        if self.directory and not os.path.exists(self.directory):
+            os.makedirs(self.directory)
+
+        for cu in self.code_units:
+            try:
+                report_fn(cu, self.coverage._analyze(cu))
+            except NoSource:
+                if not self.config.ignore_errors:
+                    raise
+            except NotPython:
+                # Only report errors for .py files, and only if we didn't
+                # explicitly suppress those errors.
+                if cu.should_be_python() and not self.config.ignore_errors:
+                    raise
diff --git a/coverage/results.py b/coverage/results.py
new file mode 100644 (file)
index 0000000..77ff2a2
--- /dev/null
@@ -0,0 +1,254 @@
+"""Results of coverage measurement."""
+
+import os
+
+from coverage.backward import iitems, set, sorted       # pylint: disable=W0622
+from coverage.misc import format_lines, join_regex, NoSource
+from coverage.parser import CodeParser
+
+
+class Analysis(object):
+    """The results of analyzing a code unit."""
+
+    def __init__(self, cov, code_unit):
+        self.coverage = cov
+        self.code_unit = code_unit
+
+        self.filename = self.code_unit.filename
+        ext = os.path.splitext(self.filename)[1]
+        source = None
+        if ext == '.py':
+            if not os.path.exists(self.filename):
+                source = self.coverage.file_locator.get_zip_data(self.filename)
+                if not source:
+                    raise NoSource("No source for code: '%s'" % self.filename)
+
+        self.parser = CodeParser(
+            text=source, filename=self.filename,
+            exclude=self.coverage._exclude_regex('exclude')
+            )
+        self.statements, self.excluded = self.parser.parse_source()
+
+        # Identify missing statements.
+        executed = self.coverage.data.executed_lines(self.filename)
+        exec1 = self.parser.first_lines(executed)
+        self.missing = sorted(set(self.statements) - set(exec1))
+
+        if self.coverage.data.has_arcs():
+            self.no_branch = self.parser.lines_matching(
+                join_regex(self.coverage.config.partial_list),
+                join_regex(self.coverage.config.partial_always_list)
+                )
+            n_branches = self.total_branches()
+            mba = self.missing_branch_arcs()
+            n_partial_branches = sum(
+                [len(v) for k,v in iitems(mba) if k not in self.missing]
+                )
+            n_missing_branches = sum([len(v) for k,v in iitems(mba)])
+        else:
+            n_branches = n_partial_branches = n_missing_branches = 0
+            self.no_branch = set()
+
+        self.numbers = Numbers(
+            n_files=1,
+            n_statements=len(self.statements),
+            n_excluded=len(self.excluded),
+            n_missing=len(self.missing),
+            n_branches=n_branches,
+            n_partial_branches=n_partial_branches,
+            n_missing_branches=n_missing_branches,
+            )
+
+    def missing_formatted(self):
+        """The missing line numbers, formatted nicely.
+
+        Returns a string like "1-2, 5-11, 13-14".
+
+        """
+        return format_lines(self.statements, self.missing)
+
+    def has_arcs(self):
+        """Were arcs measured in this result?"""
+        return self.coverage.data.has_arcs()
+
+    def arc_possibilities(self):
+        """Returns a sorted list of the arcs in the code."""
+        arcs = self.parser.arcs()
+        return arcs
+
+    def arcs_executed(self):
+        """Returns a sorted list of the arcs actually executed in the code."""
+        executed = self.coverage.data.executed_arcs(self.filename)
+        m2fl = self.parser.first_line
+        executed = [(m2fl(l1), m2fl(l2)) for (l1,l2) in executed]
+        return sorted(executed)
+
+    def arcs_missing(self):
+        """Returns a sorted list of the arcs in the code not executed."""
+        possible = self.arc_possibilities()
+        executed = self.arcs_executed()
+        missing = [
+            p for p in possible
+                if p not in executed
+                    and p[0] not in self.no_branch
+            ]
+        return sorted(missing)
+
+    def arcs_unpredicted(self):
+        """Returns a sorted list of the executed arcs missing from the code."""
+        possible = self.arc_possibilities()
+        executed = self.arcs_executed()
+        # Exclude arcs here which connect a line to itself.  They can occur
+        # in executed data in some cases.  This is where they can cause
+        # trouble, and here is where it's the least burden to remove them.
+        unpredicted = [
+            e for e in executed
+                if e not in possible
+                    and e[0] != e[1]
+            ]
+        return sorted(unpredicted)
+
+    def branch_lines(self):
+        """Returns a list of line numbers that have more than one exit."""
+        exit_counts = self.parser.exit_counts()
+        return [l1 for l1,count in iitems(exit_counts) if count > 1]
+
+    def total_branches(self):
+        """How many total branches are there?"""
+        exit_counts = self.parser.exit_counts()
+        return sum([count for count in exit_counts.values() if count > 1])
+
+    def missing_branch_arcs(self):
+        """Return arcs that weren't executed from branch lines.
+
+        Returns {l1:[l2a,l2b,...], ...}
+
+        """
+        missing = self.arcs_missing()
+        branch_lines = set(self.branch_lines())
+        mba = {}
+        for l1, l2 in missing:
+            if l1 in branch_lines:
+                if l1 not in mba:
+                    mba[l1] = []
+                mba[l1].append(l2)
+        return mba
+
+    def branch_stats(self):
+        """Get stats about branches.
+
+        Returns a dict mapping line numbers to a tuple:
+        (total_exits, taken_exits).
+        """
+
+        exit_counts = self.parser.exit_counts()
+        missing_arcs = self.missing_branch_arcs()
+        stats = {}
+        for lnum in self.branch_lines():
+            exits = exit_counts[lnum]
+            try:
+                missing = len(missing_arcs[lnum])
+            except KeyError:
+                missing = 0
+            stats[lnum] = (exits, exits - missing)
+        return stats
+
+
+class Numbers(object):
+    """The numerical results of measuring coverage.
+
+    This holds the basic statistics from `Analysis`, and is used to roll
+    up statistics across files.
+
+    """
+    # A global to determine the precision on coverage percentages, the number
+    # of decimal places.
+    _precision = 0
+    _near0 = 1.0              # These will change when _precision is changed.
+    _near100 = 99.0
+
+    def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0,
+                    n_branches=0, n_partial_branches=0, n_missing_branches=0
+                    ):
+        self.n_files = n_files
+        self.n_statements = n_statements
+        self.n_excluded = n_excluded
+        self.n_missing = n_missing
+        self.n_branches = n_branches
+        self.n_partial_branches = n_partial_branches
+        self.n_missing_branches = n_missing_branches
+
+    def set_precision(cls, precision):
+        """Set the number of decimal places used to report percentages."""
+        assert 0 <= precision < 10
+        cls._precision = precision
+        cls._near0 = 1.0 / 10**precision
+        cls._near100 = 100.0 - cls._near0
+    set_precision = classmethod(set_precision)
+
+    def _get_n_executed(self):
+        """Returns the number of executed statements."""
+        return self.n_statements - self.n_missing
+    n_executed = property(_get_n_executed)
+
+    def _get_n_executed_branches(self):
+        """Returns the number of executed branches."""
+        return self.n_branches - self.n_missing_branches
+    n_executed_branches = property(_get_n_executed_branches)
+
+    def _get_pc_covered(self):
+        """Returns a single percentage value for coverage."""
+        if self.n_statements > 0:
+            pc_cov = (100.0 * (self.n_executed + self.n_executed_branches) /
+                        (self.n_statements + self.n_branches))
+        else:
+            pc_cov = 100.0
+        return pc_cov
+    pc_covered = property(_get_pc_covered)
+
+    def _get_pc_covered_str(self):
+        """Returns the percent covered, as a string, without a percent sign.
+
+        Note that "0" is only returned when the value is truly zero, and "100"
+        is only returned when the value is truly 100.  Rounding can never
+        result in either "0" or "100".
+
+        """
+        pc = self.pc_covered
+        if 0 < pc < self._near0:
+            pc = self._near0
+        elif self._near100 < pc < 100:
+            pc = self._near100
+        else:
+            pc = round(pc, self._precision)
+        return "%.*f" % (self._precision, pc)
+    pc_covered_str = property(_get_pc_covered_str)
+
+    def pc_str_width(cls):
+        """How many characters wide can pc_covered_str be?"""
+        width = 3   # "100"
+        if cls._precision > 0:
+            width += 1 + cls._precision
+        return width
+    pc_str_width = classmethod(pc_str_width)
+
+    def __add__(self, other):
+        nums = Numbers()
+        nums.n_files = self.n_files + other.n_files
+        nums.n_statements = self.n_statements + other.n_statements
+        nums.n_excluded = self.n_excluded + other.n_excluded
+        nums.n_missing = self.n_missing + other.n_missing
+        nums.n_branches = self.n_branches + other.n_branches
+        nums.n_partial_branches = (
+            self.n_partial_branches + other.n_partial_branches
+            )
+        nums.n_missing_branches = (
+            self.n_missing_branches + other.n_missing_branches
+            )
+        return nums
+
+    def __radd__(self, other):
+        # Implementing 0+Numbers allows us to sum() a list of Numbers.
+        if other == 0:
+            return self
+        return NotImplemented
diff --git a/coverage/summary.py b/coverage/summary.py
new file mode 100644 (file)
index 0000000..c99c530
--- /dev/null
@@ -0,0 +1,86 @@
+"""Summary reporting"""
+
+import sys
+
+from coverage.report import Reporter
+from coverage.results import Numbers
+from coverage.misc import NotPython
+
+
+class SummaryReporter(Reporter):
+    """A reporter for writing the summary report."""
+
+    def __init__(self, coverage, config):
+        super(SummaryReporter, self).__init__(coverage, config)
+        self.branches = coverage.data.has_arcs()
+
+    def report(self, morfs, outfile=None):
+        """Writes a report summarizing coverage statistics per module.
+
+        `outfile` is a file object to write the summary to.
+
+        """
+        self.find_code_units(morfs)
+
+        # Prepare the formatting strings
+        max_name = max([len(cu.name) for cu in self.code_units] + [5])
+        fmt_name = "%%- %ds  " % max_name
+        fmt_err = "%s   %s: %s\n"
+        header = (fmt_name % "Name") + " Stmts   Miss"
+        fmt_coverage = fmt_name + "%6d %6d"
+        if self.branches:
+            header += " Branch BrMiss"
+            fmt_coverage += " %6d %6d"
+        width100 = Numbers.pc_str_width()
+        header += "%*s" % (width100+4, "Cover")
+        fmt_coverage += "%%%ds%%%%" % (width100+3,)
+        if self.config.show_missing:
+            header += "   Missing"
+            fmt_coverage += "   %s"
+        rule = "-" * len(header) + "\n"
+        header += "\n"
+        fmt_coverage += "\n"
+
+        if not outfile:
+            outfile = sys.stdout
+
+        # Write the header
+        outfile.write(header)
+        outfile.write(rule)
+
+        total = Numbers()
+
+        for cu in self.code_units:
+            try:
+                analysis = self.coverage._analyze(cu)
+                nums = analysis.numbers
+                args = (cu.name, nums.n_statements, nums.n_missing)
+                if self.branches:
+                    args += (nums.n_branches, nums.n_missing_branches)
+                args += (nums.pc_covered_str,)
+                if self.config.show_missing:
+                    args += (analysis.missing_formatted(),)
+                outfile.write(fmt_coverage % args)
+                total += nums
+            except KeyboardInterrupt:                   # pragma: not covered
+                raise
+            except:
+                report_it = not self.config.ignore_errors
+                if report_it:
+                    typ, msg = sys.exc_info()[:2]
+                    if typ is NotPython and not cu.should_be_python():
+                        report_it = False
+                if report_it:
+                    outfile.write(fmt_err % (cu.name, typ.__name__, msg))
+
+        if total.n_files > 1:
+            outfile.write(rule)
+            args = ("TOTAL", total.n_statements, total.n_missing)
+            if self.branches:
+                args += (total.n_branches, total.n_missing_branches)
+            args += (total.pc_covered_str,)
+            if self.config.show_missing:
+                args += ("",)
+            outfile.write(fmt_coverage % args)
+
+        return total.pc_covered
diff --git a/coverage/templite.py b/coverage/templite.py
new file mode 100644 (file)
index 0000000..c39e061
--- /dev/null
@@ -0,0 +1,166 @@
+"""A simple Python template renderer, for a nano-subset of Django syntax."""
+
+# Coincidentally named the same as http://code.activestate.com/recipes/496702/
+
+import re, sys
+
+class Templite(object):
+    """A simple template renderer, for a nano-subset of Django syntax.
+
+    Supported constructs are extended variable access::
+
+        {{var.modifer.modifier|filter|filter}}
+
+    loops::
+
+        {% for var in list %}...{% endfor %}
+
+    and ifs::
+
+        {% if var %}...{% endif %}
+
+    Comments are within curly-hash markers::
+
+        {# This will be ignored #}
+
+    Construct a Templite with the template text, then use `render` against a
+    dictionary context to create a finished string.
+
+    """
+    def __init__(self, text, *contexts):
+        """Construct a Templite with the given `text`.
+
+        `contexts` are dictionaries of values to use for future renderings.
+        These are good for filters and global values.
+
+        """
+        self.text = text
+        self.context = {}
+        for context in contexts:
+            self.context.update(context)
+
+        # Split the text to form a list of tokens.
+        toks = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)
+
+        # Parse the tokens into a nested list of operations.  Each item in the
+        # list is a tuple with an opcode, and arguments.  They'll be
+        # interpreted by TempliteEngine.
+        #
+        # When parsing an action tag with nested content (if, for), the current
+        # ops list is pushed onto ops_stack, and the parsing continues in a new
+        # ops list that is part of the arguments to the if or for op.
+        ops = []
+        ops_stack = []
+        for tok in toks:
+            if tok.startswith('{{'):
+                # Expression: ('exp', expr)
+                ops.append(('exp', tok[2:-2].strip()))
+            elif tok.startswith('{#'):
+                # Comment: ignore it and move on.
+                continue
+            elif tok.startswith('{%'):
+                # Action tag: split into words and parse further.
+                words = tok[2:-2].strip().split()
+                if words[0] == 'if':
+                    # If: ('if', (expr, body_ops))
+                    if_ops = []
+                    assert len(words) == 2
+                    ops.append(('if', (words[1], if_ops)))
+                    ops_stack.append(ops)
+                    ops = if_ops
+                elif words[0] == 'for':
+                    # For: ('for', (varname, listexpr, body_ops))
+                    assert len(words) == 4 and words[2] == 'in'
+                    for_ops = []
+                    ops.append(('for', (words[1], words[3], for_ops)))
+                    ops_stack.append(ops)
+                    ops = for_ops
+                elif words[0].startswith('end'):
+                    # Endsomething.  Pop the ops stack
+                    ops = ops_stack.pop()
+                    assert ops[-1][0] == words[0][3:]
+                else:
+                    raise SyntaxError("Don't understand tag %r" % words)
+            else:
+                ops.append(('lit', tok))
+
+        assert not ops_stack, "Unmatched action tag: %r" % ops_stack[-1][0]
+        self.ops = ops
+
+    def render(self, context=None):
+        """Render this template by applying it to `context`.
+
+        `context` is a dictionary of values to use in this rendering.
+
+        """
+        # Make the complete context we'll use.
+        ctx = dict(self.context)
+        if context:
+            ctx.update(context)
+
+        # Run it through an engine, and return the result.
+        engine = _TempliteEngine(ctx)
+        engine.execute(self.ops)
+        return "".join(engine.result)
+
+
+class _TempliteEngine(object):
+    """Executes Templite objects to produce strings."""
+    def __init__(self, context):
+        self.context = context
+        self.result = []
+
+    def execute(self, ops):
+        """Execute `ops` in the engine.
+
+        Called recursively for the bodies of if's and loops.
+
+        """
+        for op, args in ops:
+            if op == 'lit':
+                self.result.append(args)
+            elif op == 'exp':
+                try:
+                    self.result.append(str(self.evaluate(args)))
+                except:
+                    exc_class, exc, _ = sys.exc_info()
+                    new_exc = exc_class("Couldn't evaluate {{ %s }}: %s"
+                                        % (args, exc))
+                    raise new_exc
+            elif op == 'if':
+                expr, body = args
+                if self.evaluate(expr):
+                    self.execute(body)
+            elif op == 'for':
+                var, lis, body = args
+                vals = self.evaluate(lis)
+                for val in vals:
+                    self.context[var] = val
+                    self.execute(body)
+            else:
+                raise AssertionError("TempliteEngine doesn't grok op %r" % op)
+
+    def evaluate(self, expr):
+        """Evaluate an expression.
+
+        `expr` can have pipes and dots to indicate data access and filtering.
+
+        """
+        if "|" in expr:
+            pipes = expr.split("|")
+            value = self.evaluate(pipes[0])
+            for func in pipes[1:]:
+                value = self.evaluate(func)(value)
+        elif "." in expr:
+            dots = expr.split('.')
+            value = self.evaluate(dots[0])
+            for dot in dots[1:]:
+                try:
+                    value = getattr(value, dot)
+                except AttributeError:
+                    value = value[dot]
+                if hasattr(value, '__call__'):
+                    value = value()
+        else:
+            value = self.context[expr]
+        return value
diff --git a/coverage/tracer.c b/coverage/tracer.c
new file mode 100644 (file)
index 0000000..97dd113
--- /dev/null
@@ -0,0 +1,730 @@
+/* C-based Tracer for Coverage. */
+
+#include "Python.h"
+#include "compile.h"        /* in 2.3, this wasn't part of Python.h */
+#include "eval.h"           /* or this. */
+#include "structmember.h"
+#include "frameobject.h"
+
+/* Compile-time debugging helpers */
+#undef WHAT_LOG         /* Define to log the WHAT params in the trace function. */
+#undef TRACE_LOG        /* Define to log our bookkeeping. */
+#undef COLLECT_STATS    /* Collect counters: stats are printed when tracer is stopped. */
+
+#if COLLECT_STATS
+#define STATS(x)        x
+#else
+#define STATS(x)
+#endif
+
+/* Py 2.x and 3.x compatibility */
+
+#ifndef Py_TYPE
+#define Py_TYPE(o)    (((PyObject*)(o))->ob_type)
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+
+#define MyText_Type         PyUnicode_Type
+#define MyText_Check(o)     PyUnicode_Check(o)
+#define MyText_AS_BYTES(o)  PyUnicode_AsASCIIString(o)
+#define MyText_AS_STRING(o) PyBytes_AS_STRING(o)
+#define MyInt_FromLong(l)   PyLong_FromLong(l)
+
+#define MyType_HEAD_INIT    PyVarObject_HEAD_INIT(NULL, 0)
+
+#else
+
+#define MyText_Type         PyString_Type
+#define MyText_Check(o)     PyString_Check(o)
+#define MyText_AS_BYTES(o)  (Py_INCREF(o), o)
+#define MyText_AS_STRING(o) PyString_AS_STRING(o)
+#define MyInt_FromLong(l)   PyInt_FromLong(l)
+
+#define MyType_HEAD_INIT    PyObject_HEAD_INIT(NULL)  0,
+
+#endif /* Py3k */
+
+/* The values returned to indicate ok or error. */
+#define RET_OK      0
+#define RET_ERROR   -1
+
+/* An entry on the data stack.  For each call frame, we need to record the
+    dictionary to capture data, and the last line number executed in that
+    frame.
+*/
+typedef struct {
+    PyObject * file_data;  /* PyMem_Malloc'ed, a borrowed ref. */
+    int last_line;
+} DataStackEntry;
+
+/* The CTracer type. */
+
+typedef struct {
+    PyObject_HEAD
+
+    /* Python objects manipulated directly by the Collector class. */
+    PyObject * should_trace;
+    PyObject * warn;
+    PyObject * data;
+    PyObject * should_trace_cache;
+    PyObject * arcs;
+
+    /* Has the tracer been started? */
+    int started;
+    /* Are we tracing arcs, or just lines? */
+    int tracing_arcs;
+
+    /*
+        The data stack is a stack of dictionaries.  Each dictionary collects
+        data for a single source file.  The data stack parallels the call stack:
+        each call pushes the new frame's file data onto the data stack, and each
+        return pops file data off.
+
+        The file data is a dictionary whose form depends on the tracing options.
+        If tracing arcs, the keys are line number pairs.  If not tracing arcs,
+        the keys are line numbers.  In both cases, the value is irrelevant
+        (None).
+    */
+    /* The index of the last-used entry in data_stack. */
+    int depth;
+    /* The file data at each level, or NULL if not recording. */
+    DataStackEntry * data_stack;
+    int data_stack_alloc;       /* number of entries allocated at data_stack. */
+
+    /* The current file_data dictionary.  Borrowed. */
+    PyObject * cur_file_data;
+
+    /* The line number of the last line recorded, for tracing arcs.
+        -1 means there was no previous line, as when entering a code object.
+    */
+    int last_line;
+
+    /* The parent frame for the last exception event, to fix missing returns. */
+    PyFrameObject * last_exc_back;
+    int last_exc_firstlineno;
+
+#if COLLECT_STATS
+    struct {
+        unsigned int calls;
+        unsigned int lines;
+        unsigned int returns;
+        unsigned int exceptions;
+        unsigned int others;
+        unsigned int new_files;
+        unsigned int missed_returns;
+        unsigned int stack_reallocs;
+        unsigned int errors;
+    } stats;
+#endif /* COLLECT_STATS */
+} CTracer;
+
+#define STACK_DELTA    100
+
+static int
+CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
+{
+#if COLLECT_STATS
+    self->stats.calls = 0;
+    self->stats.lines = 0;
+    self->stats.returns = 0;
+    self->stats.exceptions = 0;
+    self->stats.others = 0;
+    self->stats.new_files = 0;
+    self->stats.missed_returns = 0;
+    self->stats.stack_reallocs = 0;
+    self->stats.errors = 0;
+#endif /* COLLECT_STATS */
+
+    self->should_trace = NULL;
+    self->warn = NULL;
+    self->data = NULL;
+    self->should_trace_cache = NULL;
+    self->arcs = NULL;
+
+    self->started = 0;
+    self->tracing_arcs = 0;
+
+    self->depth = -1;
+    self->data_stack = PyMem_Malloc(STACK_DELTA*sizeof(DataStackEntry));
+    if (self->data_stack == NULL) {
+        STATS( self->stats.errors++; )
+        PyErr_NoMemory();
+        return RET_ERROR;
+    }
+    self->data_stack_alloc = STACK_DELTA;
+
+    self->cur_file_data = NULL;
+    self->last_line = -1;
+
+    self->last_exc_back = NULL;
+
+    return RET_OK;
+}
+
+static void
+CTracer_dealloc(CTracer *self)
+{
+    if (self->started) {
+        PyEval_SetTrace(NULL, NULL);
+    }
+
+    Py_XDECREF(self->should_trace);
+    Py_XDECREF(self->warn);
+    Py_XDECREF(self->data);
+    Py_XDECREF(self->should_trace_cache);
+
+    PyMem_Free(self->data_stack);
+
+    Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+#if TRACE_LOG
+static const char *
+indent(int n)
+{
+    static const char * spaces =
+        "                                                                    "
+        "                                                                    "
+        "                                                                    "
+        "                                                                    "
+        ;
+    return spaces + strlen(spaces) - n*2;
+}
+
+static int logging = 0;
+/* Set these constants to be a file substring and line number to start logging. */
+static const char * start_file = "tests/views";
+static int start_line = 27;
+
+static void
+showlog(int depth, int lineno, PyObject * filename, const char * msg)
+{
+    if (logging) {
+        printf("%s%3d ", indent(depth), depth);
+        if (lineno) {
+            printf("%4d", lineno);
+        }
+        else {
+            printf("    ");
+        }
+        if (filename) {
+            PyObject *ascii = MyText_AS_BYTES(filename);
+            printf(" %s", MyText_AS_STRING(ascii));
+            Py_DECREF(ascii);
+        }
+        if (msg) {
+            printf(" %s", msg);
+        }
+        printf("\n");
+    }
+}
+
+#define SHOWLOG(a,b,c,d)    showlog(a,b,c,d)
+#else
+#define SHOWLOG(a,b,c,d)
+#endif /* TRACE_LOG */
+
+#if WHAT_LOG
+static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
+#endif
+
+/* Record a pair of integers in self->cur_file_data. */
+static int
+CTracer_record_pair(CTracer *self, int l1, int l2)
+{
+    int ret = RET_OK;
+
+    PyObject * t = Py_BuildValue("(ii)", l1, l2);
+    if (t != NULL) {
+        if (PyDict_SetItem(self->cur_file_data, t, Py_None) < 0) {
+            STATS( self->stats.errors++; )
+            ret = RET_ERROR;
+        }
+        Py_DECREF(t);
+    }
+    else {
+        STATS( self->stats.errors++; )
+        ret = RET_ERROR;
+    }
+    return ret;
+}
+
+/*
+ * The Trace Function
+ */
+static int
+CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
+{
+    int ret = RET_OK;
+    PyObject * filename = NULL;
+    PyObject * tracename = NULL;
+    #if WHAT_LOG || TRACE_LOG
+    PyObject * ascii = NULL;
+    #endif
+
+    #if WHAT_LOG
+    if (what <= sizeof(what_sym)/sizeof(const char *)) {
+        ascii = MyText_AS_BYTES(frame->f_code->co_filename);
+        printf("trace: %s @ %s %d\n", what_sym[what], MyText_AS_STRING(ascii), frame->f_lineno);
+        Py_DECREF(ascii);
+    }
+    #endif
+
+    #if TRACE_LOG
+    ascii = MyText_AS_BYTES(frame->f_code->co_filename);
+    if (strstr(MyText_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) {
+        logging = 1;
+    }
+    Py_DECREF(ascii);
+    #endif
+
+    /* See below for details on missing-return detection. */
+    if (self->last_exc_back) {
+        if (frame == self->last_exc_back) {
+            /* Looks like someone forgot to send a return event. We'll clear
+               the exception state and do the RETURN code here.  Notice that the
+               frame we have in hand here is not the correct frame for the RETURN,
+               that frame is gone.  Our handling for RETURN doesn't need the
+               actual frame, but we do log it, so that will look a little off if
+               you're looking at the detailed log.
+
+               If someday we need to examine the frame when doing RETURN, then
+               we'll need to keep more of the missed frame's state.
+            */
+            STATS( self->stats.missed_returns++; )
+            if (self->depth >= 0) {
+                if (self->tracing_arcs && self->cur_file_data) {
+                    if (CTracer_record_pair(self, self->last_line, -self->last_exc_firstlineno) < 0) {
+                        return RET_ERROR;
+                    }
+                }
+                SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "missedreturn");
+                self->cur_file_data = self->data_stack[self->depth].file_data;
+                self->last_line = self->data_stack[self->depth].last_line;
+                self->depth--;
+            }
+        }
+        self->last_exc_back = NULL;
+    }
+
+
+    switch (what) {
+    case PyTrace_CALL:      /* 0 */
+        STATS( self->stats.calls++; )
+        /* Grow the stack. */
+        self->depth++;
+        if (self->depth >= self->data_stack_alloc) {
+            STATS( self->stats.stack_reallocs++; )
+            /* We've outgrown our data_stack array: make it bigger. */
+            int bigger = self->data_stack_alloc + STACK_DELTA;
+            DataStackEntry * bigger_data_stack = PyMem_Realloc(self->data_stack, bigger * sizeof(DataStackEntry));
+            if (bigger_data_stack == NULL) {
+                STATS( self->stats.errors++; )
+                PyErr_NoMemory();
+                self->depth--;
+                return RET_ERROR;
+            }
+            self->data_stack = bigger_data_stack;
+            self->data_stack_alloc = bigger;
+        }
+
+        /* Push the current state on the stack. */
+        self->data_stack[self->depth].file_data = self->cur_file_data;
+        self->data_stack[self->depth].last_line = self->last_line;
+
+        /* Check if we should trace this line. */
+        filename = frame->f_code->co_filename;
+        tracename = PyDict_GetItem(self->should_trace_cache, filename);
+        if (tracename == NULL) {
+            STATS( self->stats.new_files++; )
+            /* We've never considered this file before. */
+            /* Ask should_trace about it. */
+            PyObject * args = Py_BuildValue("(OO)", filename, frame);
+            tracename = PyObject_Call(self->should_trace, args, NULL);
+            Py_DECREF(args);
+            if (tracename == NULL) {
+                /* An error occurred inside should_trace. */
+                STATS( self->stats.errors++; )
+                return RET_ERROR;
+            }
+            if (PyDict_SetItem(self->should_trace_cache, filename, tracename) < 0) {
+                STATS( self->stats.errors++; )
+                return RET_ERROR;
+            }
+        }
+        else {
+            Py_INCREF(tracename);
+        }
+
+        /* If tracename is a string, then we're supposed to trace. */
+        if (MyText_Check(tracename)) {
+            PyObject * file_data = PyDict_GetItem(self->data, tracename);
+            if (file_data == NULL) {
+                file_data = PyDict_New();
+                if (file_data == NULL) {
+                    STATS( self->stats.errors++; )
+                    return RET_ERROR;
+                }
+                ret = PyDict_SetItem(self->data, tracename, file_data);
+                Py_DECREF(file_data);
+                if (ret < 0) {
+                    STATS( self->stats.errors++; )
+                    return RET_ERROR;
+                }
+            }
+            self->cur_file_data = file_data;
+            /* Make the frame right in case settrace(gettrace()) happens. */
+            Py_INCREF(self);
+            frame->f_trace = (PyObject*)self;
+            SHOWLOG(self->depth, frame->f_lineno, filename, "traced");
+        }
+        else {
+            self->cur_file_data = NULL;
+            SHOWLOG(self->depth, frame->f_lineno, filename, "skipped");
+        }
+
+        Py_DECREF(tracename);
+
+        self->last_line = -1;
+        break;
+
+    case PyTrace_RETURN:    /* 3 */
+        STATS( self->stats.returns++; )
+        /* A near-copy of this code is above in the missing-return handler. */
+        if (self->depth >= 0) {
+            if (self->tracing_arcs && self->cur_file_data) {
+                int first = frame->f_code->co_firstlineno;
+                if (CTracer_record_pair(self, self->last_line, -first) < 0) {
+                    return RET_ERROR;
+                }
+            }
+
+            SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "return");
+            self->cur_file_data = self->data_stack[self->depth].file_data;
+            self->last_line = self->data_stack[self->depth].last_line;
+            self->depth--;
+        }
+        break;
+
+    case PyTrace_LINE:      /* 2 */
+        STATS( self->stats.lines++; )
+        if (self->depth >= 0) {
+            SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "line");
+            if (self->cur_file_data) {
+                /* We're tracing in this frame: record something. */
+                if (self->tracing_arcs) {
+                    /* Tracing arcs: key is (last_line,this_line). */
+                    if (CTracer_record_pair(self, self->last_line, frame->f_lineno) < 0) {
+                        return RET_ERROR;
+                    }
+                }
+                else {
+                    /* Tracing lines: key is simply this_line. */
+                    PyObject * this_line = MyInt_FromLong(frame->f_lineno);
+                    if (this_line == NULL) {
+                        STATS( self->stats.errors++; )
+                        return RET_ERROR;
+                    }
+                    ret = PyDict_SetItem(self->cur_file_data, this_line, Py_None);
+                    Py_DECREF(this_line);
+                    if (ret < 0) {
+                        STATS( self->stats.errors++; )
+                        return RET_ERROR;
+                    }
+                }
+            }
+            self->last_line = frame->f_lineno;
+        }
+        break;
+
+    case PyTrace_EXCEPTION:
+        /* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
+           without a return event.  To detect that, we'll keep a copy of the
+           parent frame for an exception event.  If the next event is in that
+           frame, then we must have returned without a return event.  We can
+           synthesize the missing event then.
+
+           Python itself fixed this problem in 2.4.  Pyexpat still has the bug.
+           I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
+           If it gets fixed, this code should still work properly.  Maybe some day
+           the bug will be fixed everywhere coverage.py is supported, and we can
+           remove this missing-return detection.
+
+           More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
+        */
+        STATS( self->stats.exceptions++; )
+        self->last_exc_back = frame->f_back;
+        self->last_exc_firstlineno = frame->f_code->co_firstlineno;
+        break;
+
+    default:
+        STATS( self->stats.others++; )
+        break;
+    }
+
+    return RET_OK;
+}
+
+/*
+ * Python has two ways to set the trace function: sys.settrace(fn), which
+ * takes a Python callable, and PyEval_SetTrace(func, obj), which takes
+ * a C function and a Python object.  The way these work together is that
+ * sys.settrace(pyfn) calls PyEval_SetTrace(builtin_func, pyfn), using the
+ * Python callable as the object in PyEval_SetTrace.  So sys.gettrace()
+ * simply returns the Python object used as the second argument to
+ * PyEval_SetTrace.  So sys.gettrace() will return our self parameter, which
+ * means it must be callable to be used in sys.settrace().
+ *
+ * So we make our self callable, equivalent to invoking our trace function.
+ *
+ * To help with the process of replaying stored frames, this function has an
+ * optional keyword argument:
+ *
+ *      def CTracer_call(frame, event, arg, lineno=0)
+ *
+ * If provided, the lineno argument is used as the line number, and the
+ * frame's f_lineno member is ignored.
+ */
+static PyObject *
+CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
+{
+    PyFrameObject *frame;
+    PyObject *what_str;
+    PyObject *arg;
+    int lineno = 0;
+    int what;
+    int orig_lineno;
+    PyObject *ret = NULL;
+
+    static char *what_names[] = {
+        "call", "exception", "line", "return",
+        "c_call", "c_exception", "c_return",
+        NULL
+        };
+
+    #if WHAT_LOG
+    printf("pytrace\n");
+    #endif
+
+    static char *kwlist[] = {"frame", "event", "arg", "lineno", NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!O|i:Tracer_call", kwlist,
+            &PyFrame_Type, &frame, &MyText_Type, &what_str, &arg, &lineno)) {
+        goto done;
+    }
+
+    /* In Python, the what argument is a string, we need to find an int
+       for the C function. */
+    for (what = 0; what_names[what]; what++) {
+        PyObject *ascii = MyText_AS_BYTES(what_str);
+        int should_break = !strcmp(MyText_AS_STRING(ascii), what_names[what]);
+        Py_DECREF(ascii);
+        if (should_break) {
+            break;
+        }
+    }
+
+    /* Save off the frame's lineno, and use the forced one, if provided. */
+    orig_lineno = frame->f_lineno;
+    if (lineno > 0) {
+        frame->f_lineno = lineno;
+    }
+
+    /* Invoke the C function, and return ourselves. */
+    if (CTracer_trace(self, frame, what, arg) == RET_OK) {
+        Py_INCREF(self);
+        ret = (PyObject *)self;
+    }
+
+    /* Clean up. */
+    frame->f_lineno = orig_lineno;
+
+done:
+    return ret;
+}
+
+static PyObject *
+CTracer_start(CTracer *self, PyObject *args_unused)
+{
+    PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
+    self->started = 1;
+    self->tracing_arcs = self->arcs && PyObject_IsTrue(self->arcs);
+    self->last_line = -1;
+
+    /* start() returns a trace function usable with sys.settrace() */
+    Py_INCREF(self);
+    return (PyObject *)self;
+}
+
+static PyObject *
+CTracer_stop(CTracer *self, PyObject *args_unused)
+{
+    if (self->started) {
+        PyEval_SetTrace(NULL, NULL);
+        self->started = 0;
+    }
+
+    return Py_BuildValue("");
+}
+
+static PyObject *
+CTracer_get_stats(CTracer *self)
+{
+#if COLLECT_STATS
+    return Py_BuildValue(
+        "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI}",
+        "calls", self->stats.calls,
+        "lines", self->stats.lines,
+        "returns", self->stats.returns,
+        "exceptions", self->stats.exceptions,
+        "others", self->stats.others,
+        "new_files", self->stats.new_files,
+        "missed_returns", self->stats.missed_returns,
+        "stack_reallocs", self->stats.stack_reallocs,
+        "stack_alloc", self->data_stack_alloc,
+        "errors", self->stats.errors
+        );
+#else
+    return Py_BuildValue("");
+#endif /* COLLECT_STATS */
+}
+
+static PyMemberDef
+CTracer_members[] = {
+    { "should_trace",       T_OBJECT, offsetof(CTracer, should_trace), 0,
+            PyDoc_STR("Function indicating whether to trace a file.") },
+
+    { "warn",               T_OBJECT, offsetof(CTracer, warn), 0,
+            PyDoc_STR("Function for issuing warnings.") },
+
+    { "data",               T_OBJECT, offsetof(CTracer, data), 0,
+            PyDoc_STR("The raw dictionary of trace data.") },
+
+    { "should_trace_cache", T_OBJECT, offsetof(CTracer, should_trace_cache), 0,
+            PyDoc_STR("Dictionary caching should_trace results.") },
+
+    { "arcs",               T_OBJECT, offsetof(CTracer, arcs), 0,
+            PyDoc_STR("Should we trace arcs, or just lines?") },
+
+    { NULL }
+};
+
+static PyMethodDef
+CTracer_methods[] = {
+    { "start",      (PyCFunction) CTracer_start,        METH_VARARGS,
+            PyDoc_STR("Start the tracer") },
+
+    { "stop",       (PyCFunction) CTracer_stop,         METH_VARARGS,
+            PyDoc_STR("Stop the tracer") },
+
+    { "get_stats",  (PyCFunction) CTracer_get_stats,    METH_VARARGS,
+            PyDoc_STR("Get statistics about the tracing") },
+
+    { NULL }
+};
+
+static PyTypeObject
+CTracerType = {
+    MyType_HEAD_INIT
+    "coverage.CTracer",        /*tp_name*/
+    sizeof(CTracer),           /*tp_basicsize*/
+    0,                         /*tp_itemsize*/
+    (destructor)CTracer_dealloc, /*tp_dealloc*/
+    0,                         /*tp_print*/
+    0,                         /*tp_getattr*/
+    0,                         /*tp_setattr*/
+    0,                         /*tp_compare*/
+    0,                         /*tp_repr*/
+    0,                         /*tp_as_number*/
+    0,                         /*tp_as_sequence*/
+    0,                         /*tp_as_mapping*/
+    0,                         /*tp_hash */
+    (ternaryfunc)CTracer_call, /*tp_call*/
+    0,                         /*tp_str*/
+    0,                         /*tp_getattro*/
+    0,                         /*tp_setattro*/
+    0,                         /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    "CTracer objects",         /* tp_doc */
+    0,                         /* tp_traverse */
+    0,                         /* tp_clear */
+    0,                         /* tp_richcompare */
+    0,                         /* tp_weaklistoffset */
+    0,                         /* tp_iter */
+    0,                         /* tp_iternext */
+    CTracer_methods,           /* tp_methods */
+    CTracer_members,           /* tp_members */
+    0,                         /* tp_getset */
+    0,                         /* tp_base */
+    0,                         /* tp_dict */
+    0,                         /* tp_descr_get */
+    0,                         /* tp_descr_set */
+    0,                         /* tp_dictoffset */
+    (initproc)CTracer_init,    /* tp_init */
+    0,                         /* tp_alloc */
+    0,                         /* tp_new */
+};
+
+/* Module definition */
+
+#define MODULE_DOC PyDoc_STR("Fast coverage tracer.")
+
+#if PY_MAJOR_VERSION >= 3
+
+static PyModuleDef
+moduledef = {
+    PyModuleDef_HEAD_INIT,
+    "coverage.tracer",
+    MODULE_DOC,
+    -1,
+    NULL,       /* methods */
+    NULL,
+    NULL,       /* traverse */
+    NULL,       /* clear */
+    NULL
+};
+
+
+PyObject *
+PyInit_tracer(void)
+{
+    PyObject * mod = PyModule_Create(&moduledef);
+    if (mod == NULL) {
+        return NULL;
+    }
+
+    CTracerType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&CTracerType) < 0) {
+        Py_DECREF(mod);
+        return NULL;
+    }
+
+    Py_INCREF(&CTracerType);
+    PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType);
+
+    return mod;
+}
+
+#else
+
+void
+inittracer(void)
+{
+    PyObject * mod;
+
+    mod = Py_InitModule3("coverage.tracer", NULL, MODULE_DOC);
+    if (mod == NULL) {
+        return;
+    }
+
+    CTracerType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&CTracerType) < 0) {
+        return;
+    }
+
+    Py_INCREF(&CTracerType);
+    PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType);
+}
+
+#endif /* Py3k */
diff --git a/coverage/version.py b/coverage/version.py
new file mode 100644 (file)
index 0000000..181ba84
--- /dev/null
@@ -0,0 +1,9 @@
+"""The version and URL for coverage.py"""
+# This file is exec'ed in setup.py, don't import anything!
+
+__version__ = "3.6"         # see detailed history in CHANGES.txt
+
+__url__ = "http://nedbatchelder.com/code/coverage"
+if max(__version__).isalpha():
+    # For pre-releases, use a version-specific URL.
+    __url__ += "/" + __version__
diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py
new file mode 100644 (file)
index 0000000..301bc86
--- /dev/null
@@ -0,0 +1,150 @@
+"""XML reporting for coverage.py"""
+
+import os, sys, time
+import xml.dom.minidom
+
+from coverage import __url__, __version__
+from coverage.backward import sorted, rpartition    # pylint: disable=W0622
+from coverage.report import Reporter
+
+def rate(hit, num):
+    """Return the fraction of `hit`/`num`, as a string."""
+    return "%.4g" % (float(hit) / (num or 1.0))
+
+
+class XmlReporter(Reporter):
+    """A reporter for writing Cobertura-style XML coverage results."""
+
+    def __init__(self, coverage, config):
+        super(XmlReporter, self).__init__(coverage, config)
+
+        self.packages = None
+        self.xml_out = None
+        self.arcs = coverage.data.has_arcs()
+
+    def report(self, morfs, outfile=None):
+        """Generate a Cobertura-compatible XML report for `morfs`.
+
+        `morfs` is a list of modules or filenames.
+
+        `outfile` is a file object to write the XML to.
+
+        """
+        # Initial setup.
+        outfile = outfile or sys.stdout
+
+        # Create the DOM that will store the data.
+        impl = xml.dom.minidom.getDOMImplementation()
+        docType = impl.createDocumentType(
+            "coverage", None,
+            "http://cobertura.sourceforge.net/xml/coverage-03.dtd"
+            )
+        self.xml_out = impl.createDocument(None, "coverage", docType)
+
+        # Write header stuff.
+        xcoverage = self.xml_out.documentElement
+        xcoverage.setAttribute("version", __version__)
+        xcoverage.setAttribute("timestamp", str(int(time.time()*1000)))
+        xcoverage.appendChild(self.xml_out.createComment(
+            " Generated by coverage.py: %s " % __url__
+            ))
+        xpackages = self.xml_out.createElement("packages")
+        xcoverage.appendChild(xpackages)
+
+        # Call xml_file for each file in the data.
+        self.packages = {}
+        self.report_files(self.xml_file, morfs)
+
+        lnum_tot, lhits_tot = 0, 0
+        bnum_tot, bhits_tot = 0, 0
+
+        # Populate the XML DOM with the package info.
+        for pkg_name in sorted(self.packages.keys()):
+            pkg_data = self.packages[pkg_name]
+            class_elts, lhits, lnum, bhits, bnum = pkg_data
+            xpackage = self.xml_out.createElement("package")
+            xpackages.appendChild(xpackage)
+            xclasses = self.xml_out.createElement("classes")
+            xpackage.appendChild(xclasses)
+            for class_name in sorted(class_elts.keys()):
+                xclasses.appendChild(class_elts[class_name])
+            xpackage.setAttribute("name", pkg_name.replace(os.sep, '.'))
+            xpackage.setAttribute("line-rate", rate(lhits, lnum))
+            xpackage.setAttribute("branch-rate", rate(bhits, bnum))
+            xpackage.setAttribute("complexity", "0")
+
+            lnum_tot += lnum
+            lhits_tot += lhits
+            bnum_tot += bnum
+            bhits_tot += bhits
+
+        xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot))
+        xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot))
+
+        # Use the DOM to write the output file.
+        outfile.write(self.xml_out.toprettyxml())
+
+        # Return the total percentage.
+        return 100.0 * (lhits_tot + bhits_tot) / (lnum_tot + bnum_tot)
+
+    def xml_file(self, cu, analysis):
+        """Add to the XML report for a single file."""
+
+        # Create the 'lines' and 'package' XML elements, which
+        # are populated later.  Note that a package == a directory.
+        package_name = rpartition(cu.name, ".")[0]
+        className = cu.name
+
+        package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0])
+
+        xclass = self.xml_out.createElement("class")
+
+        xclass.appendChild(self.xml_out.createElement("methods"))
+
+        xlines = self.xml_out.createElement("lines")
+        xclass.appendChild(xlines)
+
+        xclass.setAttribute("name", className)
+        filename = cu.file_locator.relative_filename(cu.filename)
+        xclass.setAttribute("filename", filename.replace("\\", "/"))
+        xclass.setAttribute("complexity", "0")
+
+        branch_stats = analysis.branch_stats()
+
+        # For each statement, create an XML 'line' element.
+        for line in analysis.statements:
+            xline = self.xml_out.createElement("line")
+            xline.setAttribute("number", str(line))
+
+            # Q: can we get info about the number of times a statement is
+            # executed?  If so, that should be recorded here.
+            xline.setAttribute("hits", str(int(line not in analysis.missing)))
+
+            if self.arcs:
+                if line in branch_stats:
+                    total, taken = branch_stats[line]
+                    xline.setAttribute("branch", "true")
+                    xline.setAttribute("condition-coverage",
+                        "%d%% (%d/%d)" % (100*taken/total, taken, total)
+                        )
+            xlines.appendChild(xline)
+
+        class_lines = len(analysis.statements)
+        class_hits = class_lines - len(analysis.missing)
+
+        if self.arcs:
+            class_branches = sum([t for t,k in branch_stats.values()])
+            missing_branches = sum([t-k for t,k in branch_stats.values()])
+            class_br_hits = class_branches - missing_branches
+        else:
+            class_branches = 0.0
+            class_br_hits = 0.0
+
+        # Finalize the statistics that are collected in the XML DOM.
+        xclass.setAttribute("line-rate", rate(class_hits, class_lines))
+        xclass.setAttribute("branch-rate", rate(class_br_hits, class_branches))
+        package[0][className] = xclass
+        package[1] += class_hits
+        package[2] += class_lines
+        package[3] += class_br_hits
+        package[4] += class_branches
diff --git a/doc/api.rst b/doc/api.rst
new file mode 100644 (file)
index 0000000..fe59bc2
--- /dev/null
@@ -0,0 +1,47 @@
+.. _api:
+
+============
+Coverage API
+============
+
+:history: 20090524T134300, brand new docs.
+:history: 20090613T164000, final touches for 3.0
+:history: 20100221T151500, docs for 3.3 (on the plane back from PyCon)
+:history: 20100725T211700, updated for 3.4.
+:history: 20121111T235800, added a bit of clarification.
+
+
+The API to coverage.py is very simple, contained in a single module called
+`coverage`.  Most of the interface is in a single class, also called
+`coverage`.  Methods on the coverage object correspond roughly to operations available
+in the command line interface. For example, a simple use would be::
+
+    import coverage
+
+    cov = coverage.coverage()
+    cov.start()
+
+    # .. call your code ..
+
+    cov.stop()
+    cov.save()
+
+    cov.html_report()
+
+
+The coverage module
+-------------------
+
+.. module:: coverage
+
+.. autoclass:: coverage
+    :members:
+
+
+Starting coverage automatically
+-------------------------------
+
+This function is used to start coverage measurement automatically when Python
+starts.  See :ref:`subprocess` for details.
+
+.. autofunction:: process_startup
diff --git a/doc/branch.rst b/doc/branch.rst
new file mode 100644 (file)
index 0000000..13b9dc6
--- /dev/null
@@ -0,0 +1,120 @@
+.. _branch:
+
+===========================
+Branch coverage measurement
+===========================
+
+:history: 20091127T201300, new for version 3.2
+:history: 20100725T211700, updated for 3.4.
+:history: 20110604T181700, updated for 3.5.
+:history: 20111214T181800, Fix a bug that Guido pointed out.
+
+.. highlight:: python
+   :linenothreshold: 5
+
+
+In addition to the usual statement coverage, Coverage.py also supports branch
+coverage measurement. Where a line in your program could jump to more than one
+next line, coverage.py tracks which of those destinations are actually visited,
+and flags lines that haven't visited all of their possible destinations.
+
+For example::
+
+    def my_partial_fn(x):       # line 1
+        if x:                   #      2
+            y = 10              #      3
+        return y                #      4
+
+    my_partial_fn(1)
+
+In this code, line 2 is an ``if`` statement which can go next to either line 3
+or line 4. Statement coverage would show all lines of the function as executed.
+But the if was never evaluated as false, so line 2 never jumps to line 4.
+
+Branch coverage will flag this code as not fully covered because of the missing
+jump from line 2 to line 4.  This is known as a partial branch.
+
+
+How to measure branch coverage
+------------------------------
+
+To measure branch coverage, run coverage.py with the ``--branch`` flag::
+
+    coverage run --branch myprog.py
+
+When you report on the results with ``coverage report`` or ``coverage html``,
+the percentage of branch possibilities taken will be included in the percentage
+covered total for each file.  The coverage percentage for a file is the actual
+executions divided by the execution opportunities.  Each line in the file is an
+execution opportunity, as is each branch destination.
+
+The HTML report gives information about which lines had missing branches. Lines
+that were missing some branches are shown in yellow, with an annotation at the
+far right showing branch destination line numbers that were not exercised.
+
+The XML report produced by ``coverage xml`` also includes branch information,
+including separate statement and branch coverage percentages.
+
+
+How it works
+------------
+
+When measuring branches, coverage.py collects pairs of line numbers, a source
+and destination for each transition from one line to another.  Static analysis
+of the compiled bytecode provides a list of possible transitions.  Comparing
+the measured to the possible indicates missing branches.
+
+The idea of tracking how lines follow each other was from `Titus Brown`__.
+Thanks, Titus!
+
+__ http://ivory.idyll.org/blog
+
+
+Excluding code
+--------------
+
+If you have :ref:`excluded code <excluding>`, a condtional will not be
+counted as a branch if one of its choices is excluded::
+
+    def only_one_choice(x):
+        if x:
+            blah1()
+            blah2()
+        else:       # pragma: no cover
+            # x is always true.
+            blah3()
+
+Because the ``else`` clause is excluded, the ``if`` only has one possible
+next line, so it isn't considered a branch at all.
+
+
+Structurally partial branches
+-----------------------------
+
+Sometimes branching constructs are used in unusual ways that don't actually
+branch.  For example::
+
+    while True:
+        if cond:
+            break
+        do_something()
+
+Here the while loop will never exit normally, so it doesn't take both of its
+"possible" branches.  For some of these constructs, such as "while True:" and
+"if 0:", coverage.py understands what is going on.  In these cases, the line
+will not be marked as a partial branch.
+
+But there are many ways in your own code to write intentionally partial
+branches, and you don't want coverage.py pestering you about them.  You can
+tell coverage.py that you don't want them flagged by marking them with a
+pragma::
+
+    i = 0
+    while i < 999999999:    # pragma: no branch
+        if eventually():
+            break
+
+Here the while loop will never complete because the break will always be taken
+at some point.  Coverage.py can't work that out on its own, but the
+"no branch" pragma indicates that the branch is known to be partial, and
+the line is not flagged.
diff --git a/doc/changes.rst b/doc/changes.rst
new file mode 100644 (file)
index 0000000..576c996
--- /dev/null
@@ -0,0 +1,629 @@
+.. _changes:
+
+====================================
+Major change history for coverage.py
+====================================
+
+:history: 20090524T134300, brand new docs.
+:history: 20090613T164000, final touches for 3.0
+:history: 20090706T205000, changes for 3.0.1
+:history: 20091004T170700, changes for 3.1
+:history: 20091128T072200, changes for 3.2
+:history: 20091205T161525, 3.2 final
+:history: 20100221T151900, changes for 3.3
+:history: 20100306T181400, changes for 3.3.1
+:history: 20100725T211700, updated for 3.4.
+:history: 20100820T151500, updated for 3.4b1
+:history: 20100906T133800, updated for 3.4b2
+:history: 20100919T163400, updated for 3.4 release.
+:history: 20110604T214100, updated for 3.5b1
+:history: 20110629T082200, updated for 3.5
+:history: 20110923T081600, updated for 3.5.1
+:history: 20120429T162100, updated for 3.5.2b1
+:history: 20120503T233700, updated for 3.5.2
+:history: 20120929T093100, updated for 3.5.3
+:history: 20121129T060100, updated for 3.6b1.
+:history: 20121223T180600, updated for 3.6b2.
+:history: 20130105T173500, updated for 3.6
+
+
+These are the major changes for coverage.py.  For a more complete change
+history, see the `CHANGES.txt`_ file in the source tree.
+
+.. _CHANGES.txt: http://bitbucket.org/ned/coveragepy/src/tip/CHANGES.txt
+
+
+Version 3.6 --- 5 January 2013
+------------------------------
+
+Features:
+
+- The **report**, **html**, and **xml** commands now accept a ``--fail-under``
+  switch that indicates in the exit status whether the coverage percentage was
+  less than a particular value.  Closes `issue 139`_.
+
+- The reporting functions coverage.report(), coverage.html_report(), and
+  coverage.xml_report() now all return a float, the total percentage covered
+  measurement.
+
+- The HTML report's title can now be set in the configuration file, with the
+  ``--title`` switch on the command line, or via the API.
+
+- Configuration files now support substitution of environment variables, using
+  syntax like ``${WORD}``.  Closes `issue 97`_.
+
+Packaging:
+
+- The C extension is optionally compiled using a different more widely-used
+  technique, taking another stab at fixing `issue 80`_ once and for all.
+
+- When installing, now in addition to creating a "coverage" command, two new
+  aliases are also installed.  A "coverage2" or "coverage3" command will be
+  created, depending on whether you are installing in Python 2.x or 3.x.
+  A "coverage-X.Y" command will also be created corresponding to your specific
+  version of Python.  Closes `issue 111`_.
+
+- The coverage.py installer no longer tries to bootstrap setuptools or
+  Distribute.  You must have one of them installed first, as `issue 202`_
+  recommended.
+
+- The coverage.py kit now includes docs (closing `issue 137`_) and tests.
+
+Docs:
+
+- Added a page to the docs about :doc:`contributing <contributing>` to
+  coverage.py, closing `issue 171`_.
+  
+- Added a page to the docs about :doc:`troublesome situations <trouble>`,
+  closing `issue 226`_.
+  
+- Docstrings for the legacy singleton methods are more helpful.  Thanks Marius
+  Gedminas.  Closes `issue 205`_.
+
+- The pydoc tool can now show docmentation for the class `coverage.coverage`.
+  Closes `issue 206`_.
+
+- Added some info to the TODO file, closing `issue 227`_.
+
+Fixes:
+
+- Wildcards in ``include=`` and ``omit=`` arguments were not handled properly
+  in reporting functions, though they were when running.  Now they are handled
+  uniformly, closing `issue 143`_ and `issue 163`_.  **NOTE**: it is possible
+  that your configurations may now be incorrect.  If you use ``include`` or
+  ``omit`` during reporting, whether on the command line, through the API, or
+  in a configuration file, please check carefully that you were not relying on
+  the old broken behavior.
+
+- Embarrassingly, the `[xml] output=` setting in the .coveragerc file simply
+  didn't work.  Now it does.
+
+- Combining data files would create entries for phantom files if used with
+  ``source`` and path aliases.  It no longer does.
+
+- ``debug sys`` now shows the configuration file path that was read.
+
+- If an oddly-behaved package claims that code came from an empty-string
+  filename, coverage.py no longer associates it with the directory name,
+  fixing `issue 221`_.
+
+- The XML report now consistently uses filenames for the filename attribute,
+  rather than sometimes using module names.  Fixes `issue 67`_.
+  Thanks, Marcus Cobden.
+
+- Coverage percentage metrics are now computed slightly differently under
+  branch coverage.  This means that completely unexecuted files will now
+  correctly have 0% coverage, fixing `issue 156`_.  This also means that your
+  total coverage numbers will generally now be lower if you are measuring
+  branch coverage.
+
+- On Windows, files are now reported in their correct case, fixing `issue 89`_
+  and `issue 203`_.
+
+- If a file is missing during reporting, the path shown in the error message
+  is now correct, rather than an incorrect path in the current directory.
+  Fixes `issue 60`_.
+
+- Running an HTML report in Python 3 in the same directory as an old Python 2
+  HTML report would fail with a UnicodeDecodeError. This issue (`issue 193`_)
+  is now fixed.
+
+- Fixed yet another error trying to parse non-Python files as Python, this
+  time an IndentationError, closing `issue 82`_ for the fourth time...
+
+- If `coverage xml` fails because there is no data to report, it used to
+  create a zero-length XML file.  Now it doesn't, fixing `issue 210`_.
+
+- Jython files now work with the ``--source`` option, fixing `issue 100`_.
+
+- Running coverage under a debugger is unlikely to work, but it shouldn't fail
+  with "TypeError: 'NoneType' object is not iterable".  Fixes `issue 201`_.
+
+- On some Linux distributions, when installed with the OS package manager,
+  coverage.py would report its own code as part of the results.  Now it won't,
+  fixing `issue 214`_, though this will take some time to be repackaged by the
+  operating systems.
+
+- When coverage.py ended unsuccessfully, it may have reported odd errors like
+  ``'NoneType' object has no attribute 'isabs'``.  It no longer does,
+  so kiss `issue 153`_ goodbye.
+
+
+.. _issue 60: https://bitbucket.org/ned/coveragepy/issue/60/incorrect-path-to-orphaned-pyc-files
+.. _issue 67: https://bitbucket.org/ned/coveragepy/issue/67/xml-report-filenames-may-be-generated
+.. _issue 80: https://bitbucket.org/ned/coveragepy/issue/80/is-there-a-duck-typing-way-to-know-we-cant
+.. _issue 82: https://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report
+.. _issue 89: https://bitbucket.org/ned/coveragepy/issue/89/on-windows-all-packages-are-reported-in
+.. _issue 97: https://bitbucket.org/ned/coveragepy/issue/97/allow-environment-variables-to-be
+.. _issue 100: https://bitbucket.org/ned/coveragepy/issue/100/source-directive-doesnt-work-for-packages
+.. _issue 111: https://bitbucket.org/ned/coveragepy/issue/111/when-installing-coverage-with-pip-not
+.. _issue 137: https://bitbucket.org/ned/coveragepy/issue/137/provide-docs-with-source-distribution
+.. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests
+.. _issue 143: https://bitbucket.org/ned/coveragepy/issue/143/omit-doesnt-seem-to-work-in-coverage
+.. _issue 153: https://bitbucket.org/ned/coveragepy/issue/153/non-existent-filename-triggers
+.. _issue 156: https://bitbucket.org/ned/coveragepy/issue/156/a-completely-unexecuted-file-shows-14
+.. _issue 163: https://bitbucket.org/ned/coveragepy/issue/163/problem-with-include-and-omit-filename
+.. _issue 171: https://bitbucket.org/ned/coveragepy/issue/171/how-to-contribute-and-run-tests
+.. _issue 193: https://bitbucket.org/ned/coveragepy/issue/193/unicodedecodeerror-on-htmlpy
+.. _issue 201: https://bitbucket.org/ned/coveragepy/issue/201/coverage-using-django-14-with-pydb-on
+.. _issue 202: https://bitbucket.org/ned/coveragepy/issue/202/get-rid-of-ez_setuppy-and
+.. _issue 203: https://bitbucket.org/ned/coveragepy/issue/203/duplicate-filenames-reported-when-filename
+.. _issue 205: https://bitbucket.org/ned/coveragepy/issue/205/make-pydoc-coverage-more-friendly
+.. _issue 206: https://bitbucket.org/ned/coveragepy/issue/206/pydoc-coveragecoverage-fails-with-an-error
+.. _issue 210: https://bitbucket.org/ned/coveragepy/issue/210/if-theres-no-coverage-data-coverage-xml
+.. _issue 214: https://bitbucket.org/ned/coveragepy/issue/214/coveragepy-measures-itself-on-precise
+.. _issue 221: https://bitbucket.org/ned/coveragepy/issue/221/coveragepy-incompatible-with-pyratemp
+.. _issue 226: https://bitbucket.org/ned/coveragepy/issue/226/make-readme-section-to-describe-when
+.. _issue 227: https://bitbucket.org/ned/coveragepy/issue/227/update-todo
+
+
+Version 3.5.3 --- 29 September 2012
+-----------------------------------
+
+- Line numbers in the HTML report line up better with the source lines, fixing
+  `issue 197`_, thanks Marius Gedminas.
+
+- When specifying a directory as the source= option, the directory itself no
+  longer needs to have a ``__init__.py`` file, though its subdirectories do, to
+  be considered as source files.
+
+- Files encoded as UTF-8 with a BOM are now properly handled, fixing
+  `issue 179`_.  Thanks, Pablo Carballo.
+
+- Fixed more cases of non-Python files being reported as Python source, and
+  then not being able to parse them as Python.  Closes `issue 82`_ (again).
+  Thanks, Julian Berman.
+
+- Fixed memory leaks under Python 3, thanks, Brett Cannon. Closes `issue 147`_.
+
+- Optimized .pyo files may not have been handled correctly, `issue 195`_.
+  Thanks, Marius Gedminas.
+
+- Certain unusually named file paths could have been mangled during reporting,
+  `issue 194`_.  Thanks, Marius Gedminas.
+
+- Try to do a better job of the impossible task of detecting when we can't
+  build the C extension, fixing `issue 183`_.
+
+.. _issue 82: https://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report
+.. _issue 147: https://bitbucket.org/ned/coveragepy/issue/147/massive-memory-usage-by-ctracer
+.. _issue 179: https://bitbucket.org/ned/coveragepy/issue/179/htmlreporter-fails-when-source-file-is
+.. _issue 183: https://bitbucket.org/ned/coveragepy/issue/183/install-fails-for-python-23
+.. _issue 194: https://bitbucket.org/ned/coveragepy/issue/194/filelocatorrelative_filename-could-mangle
+.. _issue 195: https://bitbucket.org/ned/coveragepy/issue/195/pyo-file-handling-in-codeunit
+.. _issue 197: https://bitbucket.org/ned/coveragepy/issue/197/line-numbers-in-html-report-do-not-align
+
+
+Version 3.5.2 --- 4 May 2012
+----------------------------
+
+- The HTML report has slightly tweaked controls: the buttons at the top of
+  the page are color-coded to the source lines they affect.
+
+- Custom CSS can be applied to the HTML report by specifying a CSS file as
+  the extra_css configuration value in the [html] section.
+
+- Source files with custom encodings declared in a comment at the top are now
+  properly handled during reporting on Python 2.  Python 3 always handled them
+  properly.  This fixes `issue 157`_.
+
+- Backup files left behind by editors are no longer collected by the source=
+  option, fixing `issue 168`_.
+
+- If a file doesn't parse properly as Python, we don't report it as an error
+  if the filename seems like maybe it wasn't meant to be Python.  This is a
+  pragmatic fix for `issue 82`_.
+
+- The ``-m`` switch on ``coverage report``, which includes missing line numbers
+  in the summary report, can now be specifed as ``show_missing`` in the
+  config file.  Closes `issue 173`_.
+
+- When running a module with ``coverage run -m <modulename>``, certain details
+  of the execution environment weren't the same as for
+  ``python -m <modulename>``.  This had the unfortunate side-effect of making
+  ``coverage run -m unittest discover`` not work if you had tests in a
+  directory named "test".  This fixes `issue 155`_.
+
+- Now the exit status of your product code is properly used as the process
+  status when running ``python -m coverage run ...``.  Thanks, JT Olds.
+
+- When installing into pypy, we no longer attempt (and fail) to compile
+  the C tracer function, closing `issue 166`_.
+
+.. _issue 82: https://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report
+.. _issue 155: https://bitbucket.org/ned/coveragepy/issue/155/cant-use-coverage-run-m-unittest-discover
+.. _issue 157: https://bitbucket.org/ned/coveragepy/issue/157/chokes-on-source-files-with-non-utf-8
+.. _issue 166: https://bitbucket.org/ned/coveragepy/issue/166/dont-try-to-compile-c-extension-on-pypy
+.. _issue 168: https://bitbucket.org/ned/coveragepy/issue/168/dont-be-alarmed-by-emacs-droppings
+.. _issue 173: https://bitbucket.org/ned/coveragepy/issue/173/theres-no-way-to-specify-show-missing-in
+
+
+Version 3.5.1 --- 23 September 2011
+-----------------------------------
+
+- When combining data files from parallel runs, you can now instruct coverage
+  about which directories are equivalent on different machines.  A ``[paths]``
+  section in the configuration file lists paths that are to be considered
+  equivalent.  Finishes `issue 17`_.
+
+- for-else constructs are understood better, and don't cause erroneous partial
+  branch warnings.  Fixes `issue 122`_.
+
+- Branch coverage for ``with`` statements is improved, fixing `issue 128`_.
+
+- The number of partial branches reported on the HTML summary page was
+  different than the number reported on the individual file pages.  This is
+  now fixed.
+
+- An explicit include directive to measure files in the Python installation
+  wouldn't work because of the standard library exclusion.  Now the include
+  directive takes precendence, and the files will be measured.  Fixes
+  `issue 138`_.
+
+- The HTML report now handles Unicode characters in Python source files
+  properly.  This fixes `issue 124`_ and `issue 144`_. Thanks, Devin
+  Jeanpierre.
+
+- In order to help the core developers measure the test coverage of the
+  standard library, Brandon Rhodes devised an aggressive hack to trick Python
+  into running some coverage code before anything else in the process.
+  See the coverage/fullcoverage directory if you are interested.
+
+.. _issue 17: http://bitbucket.org/ned/coveragepy/issue/17/support-combining-coverage-data-from
+.. _issue 122: http://bitbucket.org/ned/coveragepy/issue/122/for-else-always-reports-missing-branch
+.. _issue 124: http://bitbucket.org/ned/coveragepy/issue/124/no-arbitrary-unicode-in-html-reports-in
+.. _issue 128: http://bitbucket.org/ned/coveragepy/issue/128/branch-coverage-of-with-statement-in-27
+.. _issue 138: http://bitbucket.org/ned/coveragepy/issue/138/include-should-take-precedence-over-is
+.. _issue 144: http://bitbucket.org/ned/coveragepy/issue/144/failure-generating-html-output-for
+
+
+Version 3.5 --- 29 June 2011
+----------------------------
+
+HTML reporting:
+
+- The HTML report now has hotkeys.  Try ``n``, ``s``, ``m``, ``x``, ``b``,
+  ``p``, and ``c`` on the overview page to change the column sorting.
+  On a file page, ``r``, ``m``, ``x``, and ``p`` toggle the run, missing,
+  excluded, and partial line markings.  You can navigate the highlighted
+  sections of code by using the ``j`` and ``k`` keys for next and previous.
+  The ``1`` (one) key jumps to the first highlighted section in the file,
+  and ``0`` (zero) scrolls to the top of the file.
+
+- HTML reporting is now incremental: a record is kept of the data that
+  produced the HTML reports, and only files whose data has changed will
+  be generated.  This should make most HTML reporting faster.
+
+
+Running Python files
+
+- Modules can now be run directly using ``coverage run -m modulename``, to
+  mirror Python's ``-m`` flag.  Closes `issue 95`_, thanks, Brandon Rhodes.
+
+- ``coverage run`` didn't emulate Python accurately in one detail: the
+  current directory inserted into ``sys.path`` was relative rather than
+  absolute. This is now fixed.
+
+- Pathological code execution could disable the trace function behind our
+  backs, leading to incorrect code measurement.  Now if this happens,
+  coverage.py will issue a warning, at least alerting you to the problem.
+  Closes `issue 93`_.  Thanks to Marius Gedminas for the idea.
+
+- The C-based trace function now behaves properly when saved and restored
+  with ``sys.gettrace()`` and ``sys.settrace()``.  This fixes `issue 125`_
+  and `issue 123`_.  Thanks, Devin Jeanpierre.
+
+- Coverage.py can now be run directly from a working tree by specifying
+  the directory name to python:  ``python coverage_py_working_dir run ...``.
+  Thanks, Brett Cannon.
+
+- A little bit of Jython support: `coverage run` can now measure Jython
+  execution by adapting when $py.class files are traced. Thanks, Adi Roiban.
+
+
+Reporting
+
+- Partial branch warnings can now be pragma'd away.  The configuration option
+  ``partial_branches`` is a list of regular expressions.  Lines matching any of
+  those expressions will never be marked as a partial branch.  In addition,
+  there's a built-in list of regular expressions marking statements which should
+  never be marked as partial.  This list includes ``while True:``, ``while 1:``,
+  ``if 1:``, and ``if 0:``.
+
+- The ``--omit`` and ``--include`` switches now interpret their values more
+  usefully.  If the value starts with a wildcard character, it is used as-is.
+  If it does not, it is interpreted relative to the current directory.
+  Closes `issue 121`_.
+
+- Syntax errors in supposed Python files can now be ignored during reporting
+  with the ``-i`` switch just like other source errors.  Closes `issue 115`_.
+
+.. _issue 93: http://bitbucket.org/ned/coveragepy/issue/93/copying-a-mock-object-breaks-coverage
+.. _issue 95: https://bitbucket.org/ned/coveragepy/issue/95/run-subcommand-should-take-a-module-name
+.. _issue 115: https://bitbucket.org/ned/coveragepy/issue/115/fail-gracefully-when-reporting-on-file
+.. _issue 121: https://bitbucket.org/ned/coveragepy/issue/121/filename-patterns-are-applied-stupidly
+.. _issue 123: https://bitbucket.org/ned/coveragepy/issue/123/pyeval_settrace-used-in-way-that-breaks
+.. _issue 125: https://bitbucket.org/ned/coveragepy/issue/125/coverage-removes-decoratortoolss-tracing
+
+
+Version 3.4 --- 19 September 2010
+---------------------------------
+
+Controlling source:
+
+- BACKWARD INCOMPATIBILITY: the ``--omit`` and ``--include`` switches now take
+  file patterns rather than file prefixes, closing `issue 34`_ and `issue 36`_.
+
+- BACKWARD INCOMPATIBILITY: the `omit_prefixes` argument is gone throughout
+  coverage.py, replaced with `omit`, a list of filename patterns suitable for
+  `fnmatch`.  A parallel argument `include` controls what files are included.
+
+- The run command now has a ``--source`` switch, a list of directories or
+  module names.  If provided, coverage.py will only measure execution in those
+  source files.  The run command also now supports ``--include`` and ``--omit``
+  to control what modules it measures.  This can speed execution and reduce the
+  amount of data during reporting. Thanks Zooko.
+
+- The reporting commands (report, annotate, html, and xml) now have an
+  ``--include`` switch to restrict reporting to modules matching those file
+  patterns, similar to the existing ``--omit`` switch. Thanks, Zooko.
+
+Reporting:
+
+- Completely unexecuted files can now be included in coverage results, reported
+  as 0% covered.  This only happens if the --source option is specified, since
+  coverage.py needs guidance about where to look for source files.
+
+- Python files with no statements, for example, empty ``__init__.py`` files,
+  are now reported as having zero statements instead of one.  Fixes `issue 1`_.
+
+- Reports now have a column of missed line counts rather than executed line
+  counts, since developers should focus on reducing the missed lines to zero,
+  rather than increasing the executed lines to varying targets.  Once
+  suggested, this seemed blindingly obvious.
+
+- Coverage percentages are now displayed uniformly across reporting methods.
+  Previously, different reports could round percentages differently.  Also,
+  percentages are only reported as 0% or 100% if they are truly 0 or 100, and
+  are rounded otherwise.  Fixes `issue 41`_ and `issue 70`_.
+
+- The XML report output now properly includes a percentage for branch coverage,
+  fixing `issue 65`_ and `issue 81`_, and the report is sorted by package
+  name, fixing `issue 88`_.
+
+- The XML report is now sorted by package name, fixing `issue 88`_.
+
+- The precision of reported coverage percentages can be set with the
+  ``[report] precision`` config file setting.  Completes `issue 16`_.
+
+- Line numbers in HTML source pages are clickable, linking directly to that
+  line, which is highlighted on arrival.  Added a link back to the index page
+  at the bottom of each HTML page.
+
+Execution and measurement:
+
+- Various warnings are printed to stderr for problems encountered during data
+  measurement: if a ``--source`` module has no Python source to measure, or is
+  never encountered at all, or if no data is collected.
+
+- Doctest text files are no longer recorded in the coverage data, since they
+  can't be reported anyway.  Fixes `issue 52`_ and `issue 61`_.
+
+- Threads derived from ``threading.Thread`` with an overridden `run` method
+  would report no coverage for the `run` method.  This is now fixed, closing
+  `issue 85`_.
+
+- Programs that exited with ``sys.exit()`` with no argument weren't handled
+  properly, producing a coverage.py stack trace.  This is now fixed.
+
+- Programs that call ``os.fork`` will properly collect data from both the child
+  and parent processes.  Use ``coverage run -p`` to get two data files that can
+  be combined with ``coverage combine``.  Fixes `issue 56`_.
+
+- When measuring code running in a virtualenv, most of the system library was
+  being measured when it shouldn't have been.  This is now fixed.
+
+- Coverage can now be run as a module: ``python -m coverage``.  Thanks,
+  Brett Cannon.
+
+.. _issue 1:  http://bitbucket.org/ned/coveragepy/issue/1/empty-__init__py-files-are-reported-as-1-executable
+.. _issue 16: http://bitbucket.org/ned/coveragepy/issue/16/allow-configuration-of-accuracy-of-percentage-totals
+.. _issue 34: http://bitbucket.org/ned/coveragepy/issue/34/enhanced-omit-globbing-handling
+.. _issue 36: http://bitbucket.org/ned/coveragepy/issue/36/provide-regex-style-omit
+.. _issue 41: http://bitbucket.org/ned/coveragepy/issue/41/report-says-100-when-it-isnt-quite-there
+.. _issue 52: http://bitbucket.org/ned/coveragepy/issue/52/doctesttestfile-confuses-source-detection
+.. _issue 56: http://bitbucket.org/ned/coveragepy/issue/56/coveragepy-cant-trace-child-processes-of-a
+.. _issue 61: http://bitbucket.org/ned/coveragepy/issue/61/annotate-i-doesnt-work
+.. _issue 65: http://bitbucket.org/ned/coveragepy/issue/65/branch-option-not-reported-in-cobertura
+.. _issue 70: http://bitbucket.org/ned/coveragepy/issue/70/text-report-and-html-report-disagree-on-coverage
+.. _issue 81: http://bitbucket.org/ned/coveragepy/issue/81/xml-report-does-not-have-condition-coverage-attribute-for-lines-with-a
+.. _issue 85: http://bitbucket.org/ned/coveragepy/issue/85/threadrun-isnt-measured
+.. _issue 88: http://bitbucket.org/ned/coveragepy/issue/88/xml-report-lists-packages-in-random-order
+
+
+Version 3.3.1 --- 6 March 2010
+------------------------------
+
+- Using ``parallel=True`` in a .coveragerc file prevented reporting, but now
+  does not, fixing `issue 49`_.
+
+- When running your code with ``coverage run``, if you call ``sys.exit()``,
+  coverage.py will exit with that status code, fixing `issue 50`_.
+
+.. _issue 49: http://bitbucket.org/ned/coveragepy/issue/49
+.. _issue 50: http://bitbucket.org/ned/coveragepy/issue/50
+
+
+Version 3.3 --- 24 February 2010
+--------------------------------
+
+- Settings are now read from a .coveragerc file.  A specific file can be
+  specified on the command line with ``--rcfile=FILE``.  The name of the file
+  can be programmatically set with the ``config_file`` argument to the
+  coverage() constructor, or reading a config file can be disabled with
+  ``config_file=False``.
+
+- Added coverage.process_start to enable coverage measurement when Python
+  starts.
+
+- Parallel data file names now have a random number appended to them in
+  addition to the machine name and process id. Also, parallel data files
+  combined with ``coverage combine`` are deleted after they're combined, to
+  clean up unneeded files. Fixes `issue 40`_.
+
+- Exceptions thrown from product code run with ``coverage run`` are now
+  displayed without internal coverage.py frames, so the output is the same as
+  when the code is run without coverage.py.
+
+- Fixed `issue 39`_ and `issue 47`_.
+
+.. _issue 39: http://bitbucket.org/ned/coveragepy/issue/39
+.. _issue 40: http://bitbucket.org/ned/coveragepy/issue/40
+.. _issue 47: http://bitbucket.org/ned/coveragepy/issue/47
+
+
+Version 3.2 --- 5 December 2009
+-------------------------------
+
+- Branch coverage: coverage.py can tell you which branches didn't have both (or
+  all) choices executed, even where the choice doesn't affect which lines were
+  executed.  See :ref:`branch` for more details.
+
+- The table of contents in the HTML report is now sortable: click the headers
+  on any column.  The sorting is persisted so that subsequent reports are
+  sorted as you wish.  Thanks, `Chris Adams`_.
+
+- XML reporting has file paths that let Cobertura find the source code, fixing
+  `issue 21`_.
+
+- The ``--omit`` option now works much better than before, fixing `issue 14`_
+  and `issue 33`_.  Thanks, Danek Duvall.
+
+- Added a ``--version`` option on the command line.
+
+- Program execution under coverage is a few percent faster.
+
+- Some exceptions reported by the command line interface have been cleaned up
+  so that tracebacks inside coverage.py aren't shown.  Fixes `issue 23`_.
+
+- Fixed some problems syntax coloring sources with line continuations and
+  source with tabs: `issue 30`_ and `issue 31`_.
+
+.. _Chris Adams: http://improbable.org/chris/
+.. _issue 21: http://bitbucket.org/ned/coveragepy/issue/21
+.. _issue 23: http://bitbucket.org/ned/coveragepy/issue/23
+.. _issue 14: http://bitbucket.org/ned/coveragepy/issue/14
+.. _issue 30: http://bitbucket.org/ned/coveragepy/issue/30
+.. _issue 31: http://bitbucket.org/ned/coveragepy/issue/31
+.. _issue 33: http://bitbucket.org/ned/coveragepy/issue/33
+
+
+Version 3.1 --- 4 October 2009
+------------------------------
+
+- Python 3.1 is now supported.
+
+- Coverage.py has a new command line syntax with sub-commands.  This expands
+  the possibilities for adding features and options in the future.  The old
+  syntax is still supported.  Try ``coverage help`` to see the new commands.
+  Thanks to Ben Finney for early help.
+
+- Added an experimental ``coverage xml`` command for producing coverage reports
+  in a Cobertura-compatible XML format.  Thanks, Bill Hart.
+
+- Added the ``--timid`` option to enable a simpler slower trace function that
+  works for DecoratorTools projects, including TurboGears.  Fixed `issue 12`_
+  and `issue 13`_.
+
+- HTML reports now display syntax-colored Python source.
+
+- Added a ``coverage debug`` command for getting diagnostic information about
+  the coverage.py installation.
+
+- Source code can now be read from eggs.  Thanks, `Ross Lawley`_.  Fixes
+  `issue 25`_.
+
+.. _Ross Lawley: http://agileweb.org/
+.. _issue 25: http://bitbucket.org/ned/coveragepy/issue/25
+.. _issue 12: http://bitbucket.org/ned/coveragepy/issue/12
+.. _issue 13: http://bitbucket.org/ned/coveragepy/issue/13
+
+
+Version 3.0.1 --- 7 July 2009
+-----------------------------
+
+- Removed the recursion limit in the tracer function.  Previously, code that
+  ran more than 500 frames deep would crash.
+
+- Fixed a bizarre problem involving pyexpat, whereby lines following XML parser
+  invocations could be overlooked.
+
+- On Python 2.3, coverage.py could mis-measure code with exceptions being
+  raised.  This is now fixed.
+
+- The coverage.py code itself will now not be measured by coverage.py, and no
+  coverage modules will be mentioned in the nose ``--with-cover`` plugin.
+
+- When running source files, coverage.py now opens them in universal newline
+  mode just like Python does.  This lets it run Windows files on Mac, for
+  example.
+
+
+Version 3.0 --- 13 June 2009
+----------------------------
+
+- Coverage is now a package rather than a module.  Functionality has been split
+  into classes.
+
+- HTML reports and annotation of source files: use the new ``-b`` (browser)
+  switch.  Thanks to George Song for code, inspiration and guidance.
+
+- The trace function is implemented in C for speed.  Coverage runs are now
+  much faster.  Thanks to David Christian for productive micro-sprints and
+  other encouragement.
+
+- The minimum supported Python version is 2.3.
+
+- When using the object api (that is, constructing a coverage() object), data
+  is no longer saved automatically on process exit.  You can re-enable it with
+  the ``auto_data=True`` parameter on the coverage() constructor.
+  The module-level interface still uses automatic saving.
+
+- Code in the Python standard library is not measured by default.  If you need
+  to measure standard library code, use the ``-L`` command-line switch during
+  execution, or the ``cover_pylib=True`` argument to the coverage()
+  constructor.
+
+- API changes:
+
+  - Added parameters to coverage.__init__ for options that had been set on
+    the coverage object itself.
+
+  - Added clear_exclude() and get_exclude_list() methods for programmatic
+    manipulation of the exclude regexes.
+
+  - Added coverage.load() to read previously-saved data from the data file.
+
+  - coverage.annotate_file is no longer available.
+
+  - Removed the undocumented cache_file argument to coverage.usecache().
diff --git a/doc/cmd.rst b/doc/cmd.rst
new file mode 100644 (file)
index 0000000..6b1d15d
--- /dev/null
@@ -0,0 +1,365 @@
+.. _cmd:
+
+===========================
+Coverage command line usage
+===========================
+
+:history: 20090524T134300, brand new docs.
+:history: 20090613T164000, final touches for 3.0
+:history: 20090913T084400, new command line syntax
+:history: 20091004T170700, changes for 3.1
+:history: 20091127T200700, changes for 3.2
+:history: 20100223T200600, changes for 3.3
+:history: 20100725T211700, updated for 3.4
+:history: 20110827T212500, updated for 3.5.1, combining aliases
+:history: 20120119T075600, Added some clarification from George Paci
+:history: 20120504T091800, Added info about execution warnings, and 3.5.2 stuff.
+:history: 20120807T211600, Clarified the combine rules.
+:history: 20121003T074600, Fixed an option reference, https://bitbucket.org/ned/coveragepy/issue/200/documentation-mentions-output-xml-instead
+:history: 20121117T091000, Added command aliases.
+
+.. highlight:: console
+
+
+When you install coverage.py, a command-line script simply called ``coverage``
+is placed in your Python scripts directory.  To help with multi-version
+installs, it will also create either a ``coverage2`` or ``coverage3`` alias,
+and a ``coverage-X.Y`` alias, depending on the version of Python you're using.
+For example, when installing on Python 2.7, you will be able to use
+``coverage``, ``coverage2``, or ``coverage-2.7`` on the command line.
+
+Coverage has a number of commands which determine the action performed:
+
+* **run** -- Run a Python program and collect execution data.
+
+* **report** -- Report coverage results.
+
+* **html** -- Produce annotated HTML listings with coverage results.
+
+* **xml** -- Produce an XML report with coverage results.
+
+* **annotate** -- Annotate source files with coverage results.
+
+* **erase** -- Erase previously collected coverage data.
+
+* **combine** -- Combine together a number of data files.
+
+* **debug** -- Get diagnostic information.
+
+Help is available with the **help** command, or with the ``--help`` switch on
+any other command::
+
+    $ coverage help
+    $ coverage help run
+    $ coverage run --help
+
+Version information for coverage.py can be displayed with
+``coverage --version``.
+
+Any command can use a configuration file by specifying it with the
+``--rcfile=FILE`` command-line switch.  Any option you can set on the command
+line can also be set in the configuration file.  This can be a better way to
+control coverage.py since the configuration file can be checked into source
+control, and can provide options that other invocation techniques (like test
+runner plugins) may not offer. See :ref:`config` for more details.
+
+
+.. _cmd_execution:
+
+Execution
+---------
+
+You collect execution data by running your Python program with the **run**
+command::
+
+    $ coverage run my_program.py arg1 arg2
+    blah blah ..your program's output.. blah blah
+
+Your program runs just as if it had been invoked with the Python command line.
+Arguments after your file name are passed to your program as usual in
+``sys.argv``.  Rather than providing a filename, you can use the ``-m`` switch
+and specify an importable module name instead, just as you can with the
+Python ``-m`` switch::
+
+    $ coverage run -m packagename.modulename arg1 arg2
+    blah blah ..your program's output.. blah blah
+
+If you want :ref:`branch coverage <branch>` measurement, use the ``--branch``
+flag.  Otherwise only statement coverage is measured.
+
+You can specify the code to measure with the ``--source``, ``--include``, and
+``--omit`` switches.  See :ref:`Specifying source files <source_execution>` for
+details of their interpretation.  Remember to put options for run after "run",
+but before the program invocation::
+
+    $ coverage run --source=dir1,dir2 my_program.py arg1 arg2
+    $ coverage run --source=dir1,dir2 -m packagename.modulename arg1 arg2
+
+By default, coverage does not measure code installed with the Python
+interpreter, for example, the standard library. If you want to measure that
+code as well as your own, add the ``-L`` flag.
+
+If your coverage results seem to be overlooking code that you know has been
+executed, try running coverage again with the ``--timid`` flag.  This uses a
+simpler but slower trace method.  Projects that use DecoratorTools, including
+TurboGears, will need to use ``--timid`` to get correct results.  This option
+can also be enabled by setting the environment variable COVERAGE_OPTIONS to
+``--timid``.
+
+If you are measuring coverage in a multi-process program, or across a number of
+machines, you'll want the ``--parallel-mode`` switch to keep the data separate
+during measurement.  See :ref:`cmd_combining` below.
+
+During execution, coverage.py may warn you about conditions it detects that
+could affect the measurement process.  The possible warnings include:
+
+* "Trace function changed, measurement is likely wrong: XXX"
+
+  Coverage measurement depends on a Python setting called the trace function.
+  Other Python code in your product might change that function, which will
+  disrupt coverage.py's measurement.  This warning indicate that has happened.
+  The XXX in the message is the new trace function value, which might provide
+  a clue to the cause.
+
+* "Module XXX has no Python source"
+
+  You asked coverage.py to measure module XXX, but once it was imported, it
+  turned out not to have a corresponding .py file.  Without a .py file,
+  coverage.py can't report on missing lines.
+
+* "Module XXX was never imported"
+
+  You asked coverage.py to measure module XXX, but it was never imported by
+  your program.
+
+* "No data was collected"
+
+  Coverage.py ran your program, but didn't measure any lines as executed.
+  This could be because you asked to measure only modules that never ran,
+  or for other reasons.
+
+
+
+.. _cmd_datafile:
+
+Data file
+---------
+
+Coverage collects execution data in a file called ".coverage".  If need be, you
+can set a new file name with the COVERAGE_FILE environment variable.
+
+By default,each run of your program starts with an empty data set. If you need
+to run your program multiple times to get complete data (for example, because
+you need to supply disjoint options), you can accumulate data across runs with
+the ``-a`` flag on the **run** command.
+
+To erase the collected data, use the **erase** command::
+
+    $ coverage erase
+
+
+.. _cmd_combining:
+
+Combining data files
+--------------------
+
+If you need to collect coverage data from different machines or processes,
+coverage can combine multiple files into one for reporting. Use the ``-p`` flag
+during execution to append distinguishing information to the .coverage data
+file name.
+
+Once you have created a number of these files, you can copy them all to a single
+directory, and use the **combine** command to combine them into one .coverage
+data file::
+
+    $ coverage combine
+
+If the different machines run your code from different places in their file
+systems, coverage won't know how to combine the data.  You can tell coverage
+how the different locations correlate with a ``[paths]`` section in your
+configuration file.  See :ref:`config_paths` for details.
+
+If you are collecting and renaming your own data files, you'll need to name
+them properly for **combine** to find them.   It looks for files named after
+the data file (defaulting to ".coverage", overridable with COVERAGE_FILE), with
+a dotted suffix.  All such files in the current directory will be combined.
+Here are some examples of combinable data files::
+
+    .coverage.machine1
+    .coverage.20120807T212300
+    .coverage.last_good_run.ok
+
+
+.. _cmd_reporting:
+
+Reporting
+---------
+
+Coverage provides a few styles of reporting, with the **report**, **html**,
+**annotate**, and **xml** commands.  They share a number of common options.
+
+The command-line arguments are module or file names to report on, if you'd like
+to report on a subset of the data collected.
+
+The ``--include`` and ``--omit`` flags specify lists of filename patterns. They
+control which files to report on, and are described in more detail
+in :ref:`source`.
+
+The ``-i`` or ``--ignore-errors`` switch tells coverage.py to ignore problems
+encountered trying to find source files to report on.  This can be useful if
+some files are missing, or if your Python execution is tricky enough that file
+names are synthesized without real source files.
+
+If you provide a ``--fail-under`` value, the total percentage covered will be
+compared to that value.  If it is less, the command will exit with a status
+code of 2, indicating that the total coverage was less than your target.  This
+can be used as part of a pass/fail condition, for example in a continuous
+integration server.  This option isn't available for **annotate**.
+
+
+.. _cmd_summary:
+
+Coverage summary
+----------------
+
+The simplest reporting is a textual summary produced with **report**::
+
+    $ coverage report
+    Name                      Stmts   Miss  Cover
+    ---------------------------------------------
+    my_program                   20      4    80%
+    my_module                    15      2    86%
+    my_other_module              56      6    89%
+    ---------------------------------------------
+    TOTAL                        91     12    87%
+
+For each module executed, the report shows the count of executable statements,
+the number of those statements missed, and the resulting coverage, expressed
+as a percentage.
+
+The ``-m`` flag also shows the line numbers of missing statements::
+
+    $ coverage report -m
+    Name                      Stmts   Miss  Cover   Missing
+    -------------------------------------------------------
+    my_program                   20      4    80%   33-35, 39
+    my_module                    15      2    86%   8, 12
+    my_other_module              56      6    89%   17-23
+    -------------------------------------------------------
+    TOTAL                        91     12    87%
+
+You can restrict the report to only certain files by naming them on the
+command line::
+
+    $ coverage report -m my_program.py my_other_module.py
+    Name                      Stmts   Miss  Cover   Missing
+    -------------------------------------------------------
+    my_program                   20      4    80%   33-35, 39
+    my_other_module              56      6    89%   17-23
+    -------------------------------------------------------
+    TOTAL                        76     10    87%
+
+Other common reporting options are described above in :ref:`cmd_reporting`.
+
+
+.. _cmd_html:
+
+HTML annotation
+---------------
+
+Coverage can annotate your source code for which lines were executed
+and which were not.  The **html** command creates an HTML report similar to the
+**report** summary, but as an HTML file.  Each module name links to the source
+file decorated to show the status of each line.
+
+Here's a `sample report`__.
+
+__ /code/coverage/sample_html/index.html
+
+Lines are highlighted green for executed, red for missing, and gray for
+excluded.  The counts at the top of the file are buttons to turn on and off
+the highlighting.
+
+A number of keyboard shortcuts are available for navigating the report.
+Click the keyboard icon in the upper right to see the complete list.
+
+The title of the report can be set with the ``title`` setting in the
+``[html]`` section of the configuration file, or the ``--title`` switch on
+the command line.
+
+If you prefer a different style for your HTML report, you can provide your
+own CSS file to apply, by specifying a CSS file in the ``[html]`` section of
+the configuration file.  See :ref:`config_html` for details.
+
+The ``-d`` argument specifies an output directory, defaulting to "htmlcov"::
+
+    $ coverage html -d coverage_html
+
+Other common reporting options are described above in :ref:`cmd_reporting`.
+
+Generating the HTML report can be time-consuming.  Stored with the HTML report
+is a data file that is used to speed up reporting the next time.  If you
+generate a new report into the same directory, coverage.py will skip
+generating unchanged pages, making the process faster.
+
+
+.. _cmd_annotation:
+
+Text annotation
+---------------
+
+The **annotate** command produces a text annotation of your source code.  With a
+``-d`` argument specifying an output directory, each Python file becomes a text
+file in that directory.  Without ``-d``, the files are written into the same
+directories as the original Python files.
+
+Coverage status for each line of source is indicated with a character prefix::
+
+    > executed
+    ! missing (not executed)
+    - excluded
+
+For example::
+
+      # A simple function, never called with x==1
+
+    > def h(x):
+          """Silly function."""
+    -     if 0:   #pragma: no cover
+    -         pass
+    >     if x == 1:
+    !         a = 1
+    >     else:
+    >         a = 2
+
+Other common reporting options are described above in :ref:`cmd_reporting`.
+
+
+.. _cmd_xml:
+
+XML reporting
+-------------
+
+The **xml** command writes coverage data to a "coverage.xml" file in a format
+compatible with `Cobertura`_.
+
+.. _Cobertura: http://cobertura.sourceforge.net
+
+You can specify the name of the output file with the ``-o`` switch.
+
+Other common reporting options are described above in :ref:`cmd_reporting`.
+
+
+.. _cmd_debug:
+
+Diagnostics
+-----------
+
+The **debug** command shows internal information to help diagnose problems.
+If you are reporting a bug about coverage.py, including the output of this
+command can often help::
+
+    $ coverage debug sys > please_attach_to_bug_report.txt
+
+Two types of information are available: ``sys`` to show system configuration,
+and ``data`` to show a summary of the collected coverage data.
diff --git a/doc/config.rst b/doc/config.rst
new file mode 100644 (file)
index 0000000..f6d8f1f
--- /dev/null
@@ -0,0 +1,198 @@
+.. _config:
+
+===================
+Configuration files
+===================
+
+:history: 20100223T201600, new for 3.3
+:history: 20100725T211700, updated for 3.4.
+:history: 20100824T092900, added ``precision``.
+:history: 20110604T184400, updated for 3.5.
+:history: 20110827T212700, updated for 3.5.1
+
+
+Coverage.py options can be specified in a configuration file.  This makes it
+easier to re-run coverage with consistent settings, and also allows for
+specification of options that are otherwise only available in the
+:ref:`API <api>`.
+
+Configuration files also make it easier to get coverage testing of spawned
+sub-processes.  See :ref:`subprocess` for more details.
+
+The default name for configuration files is ``.coveragerc``, in the same
+directory coverage.py is being run in.  Most of the settings in the
+configuration file are tied to your source code and how it should be
+measured, so it should be stored with your source, and checked into
+source control, rather than put in your home directory.
+
+
+Syntax
+------
+
+A coverage.py configuration file is in classic .ini file format: sections are
+introduced by a ``[section]`` header, and contain ``name = value`` entries.
+Lines beginning with ``#`` or ``;`` are ignored as comments.
+
+Strings don't need quotes. Multi-valued strings can be created by indenting
+values on multiple lines.
+
+Boolean values can be specified as ``on``, ``off``, ``true``, ``false``, ``1``,
+or ``0`` and are case-insensitive.
+
+Environment variables can be substituted in by using dollar signs: ``$WORD``
+``${WORD}`` will be replaced with the value of ``WORD`` in the environment.
+A dollar sign can be inserted with ``$$``.  Missing environment variables
+will result in empty strings with no error.
+
+Many sections and values correspond roughly to commands and options in
+the :ref:`command-line interface <cmd>`.
+
+Here's a sample configuration file::
+
+    # .coveragerc to control coverage.py
+    [run]
+    branch = True
+
+    [report]
+    # Regexes for lines to exclude from consideration
+    exclude_lines =
+        # Have to re-enable the standard pragma
+        pragma: no cover
+
+        # Don't complain about missing debug-only code:
+        def __repr__
+        if self\.debug
+
+        # Don't complain if tests don't hit defensive assertion code:
+        raise AssertionError
+        raise NotImplementedError
+
+        # Don't complain if non-runnable code isn't run:
+        if 0:
+        if __name__ == .__main__.:
+
+    ignore_errors = True
+
+    [html]
+    directory = coverage_html_report
+
+
+[run]
+-----
+
+These values are generally used when running product code, though some apply
+to more than one command.
+
+``branch`` (boolean, default False): whether to measure
+:ref:`branch coverage <branch>` in addition to statement coverage.
+
+``cover_pylib`` (boolean, default False): whether to measure the Python
+standard library.
+
+``data_file`` (string, default ".coverage"): the name of the data file to use
+for storing or reporting coverage.
+
+``include`` (multi-string): a list of filename patterns, the files to include
+in measurement or reporting.  See :ref:`source` for details.
+
+``omit`` (multi-string): a list of filename patterns, the files to leave out
+of measurement or reporting.  See :ref:`source` for details.
+
+``parallel`` (boolean, default False): append the machine name, process
+id and random number to the data file name to simplify collecting data from
+many processes.  See :ref:`cmd_combining` for more information.
+
+``source`` (multi-string): a list of packages or directories, the source to
+measure during execution.  See :ref:`source` for details.
+
+``timid`` (boolean, default False): use a simpler but slower trace method.
+Try this if you get seemingly impossible results.
+
+
+.. _config_paths:
+
+[paths]
+-------
+
+The entries in this section are lists of file paths that should be
+considered equivalent when combining data from different machines::
+
+    [paths]
+    source =
+        src/
+        /jenkins/build/*/src
+        c:\myproj\src
+
+The names of the entries are ignored, you may choose any name that
+you like.  The value is a lists of strings.  When combining data
+with the ``combine`` command, two file paths will be combined
+if they start with paths from the same list.
+
+The first value must be an actual file path on the machine where
+the reporting will happen, so that source code can be found.
+The other values can be file patterns to match against the paths
+of collected data.
+
+See :ref:`cmd_combining` for more information.
+
+
+[report]
+--------
+
+Values common to many kinds of reporting.
+
+``exclude_lines`` (multi-string): a list of regular expressions.  Any line of
+your source code that matches one of these regexes is excluded from being
+reported as missing.  More details are in :ref:`excluding`.  If you use this
+option, you are replacing all the exclude regexes, so you'll need to also
+supply the "pragma: no cover" regex if you still want to use it.
+
+``ignore_errors`` (boolean, default False): ignore source code that can't be
+found.
+
+``include`` (multi-string): a list of filename patterns, the files to include
+in reporting.  See :ref:`source` for details.
+
+``omit`` (multi-string): a list of filename patterns, the files to leave out
+of reporting.  See :ref:`source` for details.
+
+``partial_branches`` (multi-string): a list of regular expressions.  Any line
+of code that matches one of these regexes is excused from being reported as
+a partial branch.  More details are in :ref:`branch`.  If you use this option,
+you are replacing all the partial branch regexes so you'll need to also
+supply the "pragma: no branch" regex if you still want to use it.
+
+``precision`` (integer): the number of digits after the decimal point to
+display for reported coverage percentages.  The default is 0, displaying
+for example "87%".  A value of 2 will display percentages like "87.32%".
+
+``show_missing`` (boolean, default False): when running a summary report,
+show missing lines.  See :ref:`cmd_summary` for more information.
+
+
+.. _config_html:
+
+[html]
+------
+
+Values particular to HTML reporting.  The values in the ``[report]`` section
+also apply to HTML output, where appropriate.
+
+``directory`` (string, default "htmlcov"): where to write the HTML report files.
+
+``extra_css`` (string): the path to a file of CSS to apply to the HTML report.
+The file will be copied into the HTML output directory.  Don't name it
+"style.css".  This CSS is in addition to the CSS normally used, though you can
+overwrite as many of the rules as you like.
+
+``title`` (string, default "Coverage report"): the title to use for the report.
+Note this is text, not HTML.
+
+
+[xml]
+-----
+
+Values particular to XML reporting.  The values in the ``[report]`` section
+also apply to XML output, where appropriate.
+
+``output`` (string, default "coverage.xml"): where to write the XML report.
diff --git a/doc/contributing.rst b/doc/contributing.rst
new file mode 100644 (file)
index 0000000..90b9ba1
--- /dev/null
@@ -0,0 +1,164 @@
+.. _contributing:
+
+===========================
+Contributing to coverage.py
+===========================
+
+:history: 20121112T154100, brand new docs.
+
+.. highlight:: console
+
+I welcome contributions to coverage.py.  Over the years, dozens of people have
+provided patches of various sizes to add features or fix bugs.  This page
+should have all the information you need to make a contribution.
+
+One source of history or ideas are the `bug reports`_ against coverage.py.
+There you can find ideas for requested features, or the remains of rejected
+ideas.
+
+.. _bug reports: https://bitbucket.org/ned/coveragepy/issues?status=new&status=open
+
+
+Before you begin
+----------------
+
+If you have an idea for coverage.py, run it by me before you begin writing
+code.  This way, I can get you going in the right direction, or point you to
+previous work in the area.  Things are not always as straightforward as they
+seem, and having the benefit of lessons learned by those before you can save
+you frustration.
+
+
+Getting the code
+----------------
+
+The coverage.py code is hosted on a `Mercurial`_ repository at
+https://bitbucket.org/ned/coveragepy.  To get a working environment, follow
+these steps:
+
+#.  (Optional, but recommended) Create a virtualenv to work in, and activate
+    it.
+
+#.  Clone the repo::
+
+        $ hg clone https://bitbucket.org/ned/coveragepy
+
+#.  Install the requirements::
+
+        $ pip install -r requirements.txt
+
+#.  Install a number of versions of Python.  Coverage.py supports a wide range
+    of Python versions.  The more you can test with, the more easily your code
+    can be used as-is.  If you only have one version, that's OK too, but may
+    mean more work integrating your contribution.
+
+
+Running the tests
+-----------------
+
+The tests are written as standard unittest-style tests, and are run with
+`tox`_::
+
+    $ tox
+    GLOB sdist-make: /home/ned/coverage/setup.py
+    py25 sdist-reinst: /home/ned/coverage/tox/dist/coverage-3.6b1.zip
+    py25 runtests: commands[0]
+    py25 runtests: commands[1]
+    py25 runtests: commands[2]
+    py25 runtests: commands[3]
+    py25 runtests: commands[4]
+    === Python 2.5.5 with Python tracer (/home/ned/coverage/tox/py25/bin/python) ===
+    ...........................................................................................(etc)
+    ----------------------------------------------------------------------
+    Ran 360 tests in 10.836s
+
+    OK
+    py25 runtests: commands[5]
+    py25 runtests: commands[6]
+    === Python 2.5.5 with C tracer (/home/ned/coverage/tox/py25/bin/python) ===
+    ...........................................................................................(etc)
+    ----------------------------------------------------------------------
+    Ran 360 tests in 10.044s
+
+    OK
+    py26 sdist-reinst: /home/ned/coverage/trunk/.tox/dist/coverage-3.6b1.zip
+    py26 runtests: commands[0]
+    py26 runtests: commands[1]
+    py26 runtests: commands[2]
+    py26 runtests: commands[3]
+    py26 runtests: commands[4]
+    === CPython 2.6.6 with Python tracer (/home/ned/coverage/tox/py26/bin/python) ===
+    ...........................................................................................(etc)
+    ----------------------------------------------------------------------
+    Ran 364 tests in 12.572s
+
+    OK
+    py26 runtests: commands[5]
+    py26 runtests: commands[6]
+    === CPython 2.6.6 with C tracer (/home/ned/coverage/tox/py26/bin/python) ===
+    ...........................................................................................(etc)
+    ----------------------------------------------------------------------
+    Ran 364 tests in 11.458s
+
+    OK
+    (and so on...)
+
+Tox runs the complete test suite twice for each version of Python you have
+installed.  The first run uses the Python implementation of the trace
+function, the second uses the C implementation.
+
+To limit tox to just a few versions of Python, use the ``-e`` switch::
+
+    $ tox -e py27,py33
+
+To run just a few tests, you can use nose test selector syntax::
+
+    $ tox test.test_misc:SetupPyTest.test_metadata
+
+This looks in `test/test_misc.py` to find the `SetupPyTest` class, and runs the
+`test_metadata` test method.
+
+Of course, run all the tests on every version of Python you have, before
+submitting a change.
+
+
+Lint, etc
+---------
+
+I try to keep the coverage.py as clean as possible.  I use pylint to alert me
+to possible problems::
+
+    $ make lint
+    pylint --rcfile=.pylintrc coverage setup.py test
+    python -m tabnanny coverage setup.py test
+    python igor.py check_eol
+
+The source is pylint-clean, even if it's because there are pragmas quieting
+some warnings.  Please try to keep it that way, but don't let pylint warnings
+keep you from sending patches.  I can clean them up.
+
+
+Coverage testing coverage.py
+----------------------------
+
+Coverage.py can measure itself, but it's complicated.  The process has been
+packaged up to make it easier::
+
+    $ COVERAGE_COVERAGE=yes tox
+    $ python igor.py combine_html
+
+Then look at htmlcov/index.html.  Note that due to the recursive nature of
+coverage.py measuring itself, there are some parts of the code that will never
+appear as covered, even though they are executed.
+
+
+Contributing
+------------
+
+When you are ready to contribute a change, any way you can get it to me is
+probably fine.  A pull request on Bitbucket is great, but a simple diff or
+patch is great too.
+
+
+.. _Mercurial: http://mercurial.selenic.com/
+.. _tox: http://tox.testrun.org/
diff --git a/doc/excluding.rst b/doc/excluding.rst
new file mode 100644 (file)
index 0000000..f1262c3
--- /dev/null
@@ -0,0 +1,105 @@
+.. _excluding:
+
+============================
+Excluding code from coverage
+============================
+
+:history: 20090613T090500, brand new docs.
+:history: 20100224T200900, updated for 3.3.
+:history: 20100725T211700, updated for 3.4.
+:history: 20110604T184400, updated for 3.5.
+
+
+You may have code in your project that you know won't be executed, and you want
+to tell coverage to ignore it.  For example, you may have debugging-only code
+that won't be executed during your unit tests. You can tell coverage to exclude
+this code during reporting so that it doesn't clutter your reports with noise
+about code that you don't need to hear about.
+
+Coverage will look for comments marking clauses for exclusion.  In this code,
+the "if debug" clause is excluded from reporting::
+
+    a = my_function1()
+    if debug:   # pragma: no cover
+        msg = "blah blah"
+        log_message(msg, a)
+    b = my_function2()
+
+Any line with a comment of "pragma: no cover" is excluded.  If that line
+introduces a clause, for example, an if clause, or a function or class
+definition, then the entire clause is also excluded.  Here the __repr__
+function is not reported as missing::
+
+    class MyObject(object):
+        def __init__(self):
+            blah1()
+            blah2()
+
+        def __repr__(self): # pragma: no cover
+            return "<MyObject>"
+
+Excluded code is executed as usual, and its execution is recorded in the
+coverage data as usual. When producing reports though, coverage excludes it from
+the list of missing code.
+
+
+Branch coverage
+---------------
+
+When measuring :ref:`branch coverage <branch>`, a condtional will not be
+counted as a branch if one of its choices is excluded::
+
+    def only_one_choice(x):
+        if x:
+            blah1()
+            blah2()
+        else:       # pragma: no cover
+            # x is always true.
+            blah3()
+
+Because the ``else`` clause is excluded, the ``if`` only has one possible
+next line, so it isn't considered a branch at all.
+
+
+Advanced exclusion
+------------------
+
+Coverage identifies exclusions by matching lines against a list of regular
+expressions. Using :ref:`configuration files <config>` or the coverage
+:ref:`API <api>`, you can add to that list. This is useful if you have
+often-used constructs to exclude that can be matched with a regex. You can
+exclude them all at once without littering your code with exclusion pragmas.
+
+For example, you might decide that __repr__ functions are usually only used
+in debugging code, and are uninteresting to test themselves.  You could exclude
+all of them by adding a regex to the exclusion list::
+
+    [report]
+    exclude_lines = def __repr__
+
+For example, here's a list of exclusions I've used::
+
+    [report]
+    exclude_lines =
+        pragma: no cover
+        def __repr__
+        if self.debug:
+        if settings.DEBUG
+        raise AssertionError
+        raise NotImplementedError
+        if 0:
+        if __name__ == .__main__.:
+
+Note that when using the ``exclude_lines`` option in a configuration file, you
+are taking control of the entire list of regexes, so you need to re-specify the
+default "pragma: no cover" match if you still want it to apply.
+
+A similar pragma, "no branch", can be used to tailor branch coverage
+measurement.  See :ref:`branch` for details.
+
+
+Excluding source files
+----------------------
+
+See :ref:`source` for ways to limit what files coverage.py measures or reports
+on.
diff --git a/doc/faq.rst b/doc/faq.rst
new file mode 100644 (file)
index 0000000..6b0c4af
--- /dev/null
@@ -0,0 +1,116 @@
+.. _faq:
+
+==================
+FAQ and other help
+==================
+
+:history: 20090613T141800, brand new docs.
+:history: 20091005T073900, updated for 3.1.
+:history: 20091127T201500, updated for 3.2.
+:history: 20110605T175500, add the announcement mailing list.
+:history: 20121231T104700, Tweak the py3 text.
+
+
+Frequently asked questions
+--------------------------
+
+**Q: I use nose to run my tests, and its cover plugin doesn't let me create
+HTML or XML reports.  What should I do?**
+
+First run your tests and collect coverage data with `nose`_ and its plugin.
+This will write coverage data into a .coverage file.  Then run coverage.py from
+the :ref:`command line <cmd>` to create the reports you need from that data.
+
+.. _nose: http://somethingaboutorange.com/mrl/projects/nose
+
+
+**Q: Why do unexecutable lines show up as executed?**
+
+Usually this is because you've updated your code and run coverage on it
+again without erasing the old data.  Coverage records line numbers executed, so
+the old data may have recorded a line number which has since moved, causing
+coverage to claim a line has been executed which cannot be.
+
+If you are using the ``-x`` command line action, it doesn't erase first by
+default.  Switch to the ``coverage run`` command, or use the ``-e`` switch to
+erase all data before starting the next run.
+
+
+**Q: Why do the bodies of functions (or classes) show as executed, but the def
+lines do not?**
+
+This happens because coverage is started after the functions are defined.  The
+definition lines are executed without coverage measurement, then coverage is
+started, then the function is called.  This means the body is measured, but
+the definition of the function itself is not.
+
+To fix this, start coverage earlier.  If you use the :ref:`command line <cmd>`
+to run your program with coverage, then your entire program will be monitored.
+If you are using the :ref:`API <api>`, you need to call coverage.start() before
+importing the modules that define your functions.
+
+
+**Q: Does coverage.py work on Python 3.x?**
+
+Yes, Python 3 is fully supported.
+
+
+**Q: Isn't coverage testing the best thing ever?**
+
+It's good, but `it isn't perfect`__.
+
+__ http://nedbatchelder.com/blog/200710/flaws_in_coverage_measurement.html
+
+
+..  Other resources
+    ---------------
+
+    There are a number of projects that help integrate coverage.py into other
+    systems:
+
+    - `trialcoverage`_ is a plug-in for Twisted trial.
+
+    .. _trialcoverage: http://pypi.python.org/pypi/trialcoverage
+
+    - `pytest-coverage`_
+
+    .. _pytest-coverage: http://pypi.python.org/pypi/pytest-coverage
+
+    - `django-coverage`_ for use with Django.
+
+    .. _django-coverage: http://pypi.python.org/pypi/django-coverage
+
+
+**Q: Where can I get more help with coverage.py?**
+
+You can discuss coverage.py or get help using it on the `Testing In Python`_
+mailing list.
+
+.. _Testing In Python: http://lists.idyll.org/listinfo/testing-in-python
+
+Bug reports are gladly accepted at the `Bitbucket issue tracker`_.
+
+.. _Bitbucket issue tracker: http://bitbucket.org/ned/coveragepy/issues
+
+Announcements of new coverage.py releases are sent to the
+`coveragepy-announce`_ mailing list.
+
+.. _coveragepy-announce: http://groups.google.com/group/coveragepy-announce
+
+`I can be reached`__ in a number of ways, I'm happy to answer questions about
+using coverage.py.  I'm also available hourly for consultation or custom
+development.
+
+__  http://nedbatchelder.com/site/aboutned.html
+
+
+History
+-------
+
+Coverage.py was originally written by `Gareth Rees`_.
+Since 2004, `Ned Batchelder`_ has extended and maintained it with the help of
+`many others`_.  The :ref:`change history <changes>` has all the details.
+
+.. _Gareth Rees:    http://garethrees.org/
+.. _Ned Batchelder: http://nedbatchelder.com
+.. _many others:    http://bitbucket.org/ned/coveragepy/src/tip/AUTHORS.txt
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644 (file)
index 0000000..ed163b8
--- /dev/null
@@ -0,0 +1,171 @@
+===========
+coverage.py
+===========
+
+:history: 20090524T134300, brand new docs.
+:history: 20090613T164000, final touches for 3.0
+:history: 20090618T195900, minor tweaks
+:history: 20090707T205200, changes for 3.0.1
+:history: 20090913T084400, new command line syntax
+:history: 20091004T211900, version 3.1
+:history: 20091127T155100, version 3.2
+:history: 20091205T161429, version 3.2 for real.
+:history: 20100224T204700, version 3.3
+:history: 20100306T181500, version 3.3.1
+:history: 20100725T211700, updated for 3.4.
+:history: 20100820T151500, updated for 3.4b1.
+:history: 20100906T134700, updated for 3.4b2.
+:history: 20100919T163500, updated for 3.4 release.
+:history: 20110213T081200, claim true 3.2 compatibility.
+:history: 20110604T114800, update for 3.5b1
+:history: 20110629T082300, update for 3.5
+:history: 20110827T221800, update for 3.5.1b1
+:history: 20110923T081800, update for 3.5.1
+:history: 20120429T162100, updated for 3.5.2b1
+:history: 20120503T233800, updated for 3.5.2
+:history: 20120929T093500, updated for 3.5.3
+:history: 20121117T094900, Change from easy_install to pip.
+:history: 20121128T203700, Updated for 3.6b1.
+:history: 20121223T180600, Updated for 3.6b2.
+:history: 20121229T112300, Updated for 3.6b3.
+:history: 20130105T174000, Updated for 3.6
+
+
+Coverage.py is a tool for measuring code coverage of Python programs. It
+monitors your program, noting which parts of the code have been executed, then
+analyzes the source to identify code that could have been executed but was not.
+
+Coverage measurement is typically used to gauge the effectiveness of tests. It
+can show which parts of your code are being exercised by tests, and which are
+not.
+
+.. ifconfig:: not prerelease
+
+    The latest version is coverage.py 3.6, released 5 January 2013.
+    It is supported on Python versions 2.3 through 3.3, and PyPy 1.8.
+
+.. ifconfig:: prerelease
+
+    The latest version is coverage.py 3.6b3, released 29 December 2012.
+    It is supported on Python versions 2.3 through 3.3, and PyPy 1.9.
+    **This is a pre-release build.  The usual warnings about possible bugs apply.**
+    The latest stable version is coverage.py 3.5.3, `described here`_.
+
+.. _described here: http://nedbatchelder.com/code/coverage
+
+
+Quick start
+-----------
+
+Getting started is easy:
+
+#.  Install coverage.py from the `coverage page on the Python Package Index`_,
+    or by using "pip install coverage".  For a few more details, see
+    :ref:`install`.
+
+#.  Use ``coverage run`` to run your program and gather data:
+
+    .. code-block:: console
+
+        $ coverage run my_program.py arg1 arg2
+        blah blah ..your program's output.. blah blah
+
+#.  Use ``coverage report`` to report on the results:
+
+    .. code-block:: console
+
+        $ coverage report -m
+        Name                      Stmts   Miss  Cover   Missing
+        -------------------------------------------------------
+        my_program                   20      4    80%   33-35, 39
+        my_other_module              56      6    89%   17-23
+        -------------------------------------------------------
+        TOTAL                        76     10    87%
+
+#.  For a nicer presentation, use ``coverage html`` to get annotated HTML
+    listings detailing missed lines:
+
+    .. code-block:: console
+
+        $ coverage html
+
+    .. ifconfig:: not prerelease
+
+        Then visit htmlcov/index.html in your browser, to see a
+        `report like this`_.
+
+    .. ifconfig:: prerelease
+
+        Then visit htmlcov/index.html in your browser, to see a
+        `report like this one`_.
+
+.. _coverage page on the Python Package Index: http://pypi.python.org/pypi/coverage
+.. _report like this: /code/coverage/sample_html/index.html
+.. _report like this one: /code/coverage/sample_html_beta/index.html
+
+
+Using coverage.py
+-----------------
+
+There are a few different ways to use coverage.py.  The simplest is the
+:ref:`command line <cmd>`, which lets you run your program and see the results.
+If you need more control over how your project is measured, you can use the
+:ref:`API <api>`.
+
+Some test runners provide coverage integration to make it easy to use coverage
+while running tests.  For example, `nose`_ has a `cover plug-in`_.
+
+You can fine-tune coverage's view of your code by directing it to ignore parts
+that you know aren't interesting.  See :ref:`source` and :ref:`excluding` for
+details.
+
+.. _nose:           http://somethingaboutorange.com/mrl/projects/nose
+.. _cover plug-in:  https://nose.readthedocs.org/en/latest/plugins/cover.html
+
+
+.. _contact:
+
+Getting help
+------------
+
+If the :ref:`FAQ <faq>` doesn't answer your question, you can discuss
+coverage.py or get help using it on the `Testing In Python`_ mailing list.
+
+.. _Testing In Python: http://lists.idyll.org/listinfo/testing-in-python
+
+Bug reports are gladly accepted at the `Bitbucket issue tracker`_.
+Bitbucket also hosts the `code repository`_.
+
+.. _Bitbucket issue tracker: http://bitbucket.org/ned/coveragepy/issues
+.. _code repository: http://bitbucket.org/ned/coveragepy
+
+`I can be reached`_ in a number of ways. I'm happy to answer questions about
+using coverage.py.  I'm also available hourly for consultation or custom
+development.
+
+.. _I can be reached:  http://nedbatchelder.com/site/aboutned.html
+
+
+
+More information
+----------------
+
+.. toctree::
+    :maxdepth: 1
+
+    install
+    cmd
+    config
+    source
+    excluding
+    branch
+    subprocess
+    api
+    contributing
+    trouble
+    faq
+    changes
+
+
+.. How it works
+.. .coverage file format
diff --git a/doc/install.rst b/doc/install.rst
new file mode 100644 (file)
index 0000000..9539093
--- /dev/null
@@ -0,0 +1,80 @@
+.. _install:
+
+============
+Installation
+============
+
+:history: 20100725T225600, new for 3.4.
+:history: 20100820T151500, updated for 3.4b1.
+:history: 20100906T134800, updated for 3.4b2.
+:history: 20110604T213400, updated for 3.5b1.
+:history: 20110629T082400, updated for 3.5.
+:history: 20110923T081900, updated for 3.5.1.
+:history: 20120429T162500, updated for 3.5.2b1.
+:history: 20120503T234000, updated for 3.5.2.
+:history: 20120929T093600, updated for 3.5.3.
+:history: 20121117T095000, Now setuptools is a pre-req.
+:history: 20121128T203000, updated for 3.6b1.
+:history: 20121223T180800, updated for 3.6b2.
+:history: 20121229T112400, updated for 3.6b3.
+:history: 20130105T174400, updated for 3.6.
+
+
+.. highlight:: console
+.. _coverage_pypi: http://pypi.python.org/pypi/coverage
+.. _setuptools: http://pypi.python.org/pypi/setuptools
+.. _Distribute: http://packages.python.org/distribute/
+
+
+Installing coverage.py is done in the usual ways. You must have `setuptools`_
+or `Distribute`_ installed already, and then you:
+
+#.  Download the appropriate kit from the
+    `coverage page on the Python Package Index`__.
+
+#.  Run ``python setup.py install``.
+
+or, use::
+
+    $ pip install coverage
+
+or even::
+
+    $ easy_install coverage
+
+.. __: coverage_pypi_
+
+
+Installing from source
+----------------------
+
+Coverage.py includes a C extension for speed. If you are installing from source,
+you may need to install the python-dev support files, for example with::
+
+    $ sudo apt-get install python-dev
+
+
+Installing on Windows
+---------------------
+
+For Windows, kits are provided on the `PyPI page`__ for different versions of
+Python and different CPU architectures. These kits require that `setuptools`_ be
+installed as a pre-requisite, but otherwise are self-contained.  They have the
+C extension pre-compiled so there's no need to worry about compilers.
+
+.. __: coverage_pypi_
+
+
+Checking the installation
+-------------------------
+
+If all went well, you should be able to open a command prompt, and see
+coverage installed properly::
+
+    $ coverage --version
+    Coverage.py, version 3.6.  http://nedbatchelder.com/code/coverage
+
+You can also invoke coverage as a module::
+
+    $ python -m coverage --version
+    Coverage.py, version 3.6.  http://nedbatchelder.com/code/coverage
diff --git a/doc/source.rst b/doc/source.rst
new file mode 100644 (file)
index 0000000..aafb976
--- /dev/null
@@ -0,0 +1,74 @@
+.. _source:
+
+=======================
+Specifying source files
+=======================
+
+:history: 20100725T172000, new in 3.4
+
+
+When coverage.py is running your program and measuring its execution, it needs
+to know what code to measure and what code not to.  Measurement imposes a speed
+penalty, and the collected data must be stored in memory and then on disk.
+More importantly, when reviewing your coverage reports, you don't want to be
+distracted with modules that aren't your concern.
+
+Coverage.py has a number of ways you can focus it in on the code you care
+about.
+
+
+.. _source_execution:
+
+Execution
+---------
+
+When running your code, the ``coverage run`` command will by default measure
+all code, unless it is part of the Python standard library.
+
+You can specify source to measure with the ``--source`` command-line switch,
+or the ``[run] source`` configuration value.  The value is a list of directories
+or package names.  If specified, only source inside these directories or
+packages will be measured.  Specifying the source option also enables
+coverage.py to report on unexecuted files, since it can search the source tree
+for files that haven't been measured at all.
+
+You can further fine-tune coverage.py's attention with the ``--include`` and
+``--omit`` switches (or ``[run] include`` and ``[run] omit`` configuration
+values). ``--include`` is a list of filename patterns. If specified, only files
+matching those patterns will be measured. ``--omit`` is also a list of filename
+patterns, specifying files not to measure.  If both ``include`` and ``omit``
+are specified, first the set of files is reduced to only those that match the
+include patterns, then any files that match the omit pattern are removed from
+the set.
+
+The ``include`` and ``omit`` filename patterns follow typical shell syntax:
+``*`` matches any number of characters and ``?`` matches a single character.
+Patterns that start with a wildcard character are used as-is, other patterns
+are interpreted relative to the current directory.
+
+The ``source``, ``include``, and ``omit`` values all work together to determine
+the source that will be measured.
+
+
+.. _source_reporting:
+
+Reporting
+---------
+
+Once your program is measured, you can specify the source files you want
+reported.  Usually you want to see all the code that was measured, but if you
+are measuring a large project, you may want to get reports for just certain
+parts.
+
+The report commands (``report``, ``html``, ``annotate``, and ``xml``) all take
+optional ``modules`` arguments, and ``--include`` and ``--omit`` switches. The
+``modules`` arguments specify particular modules to report on.  The ``include``
+and ``omit`` values are lists of filename patterns, just as with the ``run``
+command.
+
+Remember that the reporting commands can only report on the data that has been
+collected, so the data you're looking for may not be in the data available for
+reporting.
+
+Note that these are ways of specifying files to measure.  You can also exclude
+individual source lines.  See :ref:`excluding` for details.
diff --git a/doc/subprocess.rst b/doc/subprocess.rst
new file mode 100644 (file)
index 0000000..15fa4c2
--- /dev/null
@@ -0,0 +1,66 @@
+.. _subprocess:
+
+======================
+Measuring subprocesses
+======================
+
+:history: 20100224T201800, new for 3.3.
+:history: 20100725T211700, updated for 3.4.
+
+
+Complex test suites may spawn subprocesses to run tests, either to run them in
+parallel, or because subprocess behavior is an important part of the system
+under test. Measuring coverage in those subprocesses can be tricky because you
+have to modify the code spawning the process to invoke coverage.py.
+
+There's an easier way to do it: coverage.py includes a function,
+:func:`coverage.process_startup` designed to be invoked when Python starts.  It
+examines the ``COVERAGE_PROCESS_START`` environment variable, and if it is set,
+begins coverage measurement. The environment variable's value will be used as
+the name of the :ref:`configuration file <config>` to use.
+
+When using this technique, be sure to set the parallel option to true so that
+multiple coverage.py runs will each write their data to a distinct file.
+
+
+Configuring Python for subprocess coverage
+------------------------------------------
+
+Measuring coverage in subprocesses is a little tricky.  When you spawn a
+subprocess, you are invoking Python to run your program.  Usually, to get
+coverage measurement, you have to use coverage.py to run your program.
+Your subprocess won't be using coverage.py, so we have to convince Python
+to use coverage even when not explicitly invokved.
+
+To do that, we'll configure Python to run a little coverage.py code when it
+starts.  That code will look for an environment variable that tells it to
+start coverage measurement at the start of the process.
+
+To arrange all this, you have to do two things: set a value for the
+``COVERAGE_PROCESS_START`` environment variable, and then configure Python to
+invoke :func:`coverage.process_startup` when Python processes start.
+
+How you set ``COVERAGE_PROCESS_START`` depends on the details of how you create
+subprocesses.  As long as the environment variable is visible in your subprocess,
+it will work.
+
+You can configure your Python installation to invoke the ``process_startup``
+function in two ways:
+
+#. Create or append to sitecustomize.py to add these lines::
+
+    import coverage
+    coverage.process_startup()
+
+#. Create a .pth file in your Python installation containing::
+
+    import coverage; coverage.process_startup()
+
+The sitecustomize.py technique is cleaner, but may involve modifying an existing
+sitecustomize.py, since there can be only one.  If there is no sitecustomize.py
+already, you can create it in any directory on the Python path.
+
+The .pth technique seems like a hack, but works, and is documented behavior.
+On the plus side, you can create the file with any name you like so you don't
+have to coordinate with other .pth files.  On the minus side, you have to create
+the file in a system-defined directory, so you may need privileges to write it.
diff --git a/doc/trouble.rst b/doc/trouble.rst
new file mode 100644 (file)
index 0000000..c7693c9
--- /dev/null
@@ -0,0 +1,88 @@
+.. _trouble:
+
+=========================
+Things that cause trouble
+=========================
+
+:history: 20121231T085200, brand new docs.
+
+Coverage works well, and I want it to properly measure any Python program, but
+there are some situations it can't cope with.  This page details some known
+problems, with possible courses of action, and links to coverage.py bug reports
+with more information.
+
+I would love to :ref:`hear from you <contact>` if you have information about any of
+these problems, even just to explain to me why you want them to start working
+properly.
+
+If your problem isn't discussed here, you can of course search the `coverage.py
+bug tracker`_ directly to see if there is some mention of it.
+
+.. _coverage.py bug tracker: https://bitbucket.org/ned/coveragepy/issues?status=new&status=open
+
+
+Things that don't work
+----------------------
+
+There are a number of popular modules, packages, and libraries that prevent
+coverage.py from working properly:
+
+* `execv`_, or one of its variants.  These end the current program and replace
+  it with a new one.  This doesn't save the collected coverage data, so your
+  program that calls execv will not be fully measured.  A patch for coverage.py
+  is in `issue 43`_.
+
+* `multiprocessing`_ launches processes to provide parallelism.  These
+  processes don't get measured by coverage.py.  Some possible fixes are
+  discussed or linked to in `issue 117`_.
+
+* `gevent`_, which is based on `greenlet`_, and is similar to `eventlet`_. All
+  of these manipulate the C stack, and therefore confuse coverage.py.
+  `Issue 149`_ has some pointers to more information.
+
+* `sys.settrace`_ is the Python feature that coverage.py uses to see what's
+  happening in your program.  If another part of your program is using
+  sys.settrace, then it will conflict with coverage.py, and it won't be
+  measured properly.
+
+.. _execv: http://docs.python.org/library/os#os.execl
+.. _multiprocessing: http://docs.python.org/library/multiprocessing.html
+.. _gevent: http://www.gevent.org/
+.. _greenlet: http://greenlet.readthedocs.org/
+.. _eventlet: http://eventlet.net/
+.. _sys.settrace: docs.python.org/library/sys.html#sys.settrace
+.. _issue 43: https://bitbucket.org/ned/coveragepy/issue/43/coverage-measurement-fails-on-code
+.. _issue 117: https://bitbucket.org/ned/coveragepy/issue/117/enable-coverage-measurement-of-code-run-by
+.. _issue 149: https://bitbucket.org/ned/coveragepy/issue/149/coverage-gevent-looks-broken
+
+
+Things that require --timid
+---------------------------
+
+Some packages interfere with coverage measurement, but you might be able to
+make it work by using the ``--timid`` command-line switch, or the ``[run]
+timid=True`` configuration option.
+
+* `DecoratorTools`_, or any package which uses it, notably `TurboGears`_.
+  DecoratorTools fiddles with the trace function.  You  will need to use
+  ``--timid``.
+
+.. _DecoratorTools: http://pypi.python.org/pypi/DecoratorTools
+.. _TurboGears: http://turbogears.org/
+
+
+Really obscure things
+---------------------
+
+* Python 2.5 had a bug (`1569356`_) that could make your program behave
+  differently when being measured with coverage.  This is diagnosed in `issue 51`_.
+
+.. _issue 51: http://bitbucket.org/ned/coveragepy/issue/51/turbogears-15-test-failing-with-coverage
+.. _1569356: http://bugs.python.org/issue1569356
+
+
+Still having trouble?
+---------------------
+
+If your problem isn't mentioned here, and isn't already reported in the `coverage.py bug tracker`_,
+please :ref:`get in touch with me <contact>`, we'll figure out a solution.
diff --git a/igor.py b/igor.py
new file mode 100644 (file)
index 0000000..1d6c2af
--- /dev/null
+++ b/igor.py
@@ -0,0 +1,255 @@
+"""Helper for building, testing, and linting coverage.py.
+
+To get portability, all these operations are written in Python here instead
+of in shell scripts, batch files, or Makefiles.
+
+"""
+
+import fnmatch
+import glob
+import inspect
+import os
+import platform
+import socket
+import sys
+import zipfile
+
+
+def do_remove_extension():
+    """Remove the compiled C extension, no matter what its name."""
+
+    so_patterns = """
+        tracer.so
+        tracer.*.so
+        tracer.pyd
+        """.split()
+
+    for pattern in so_patterns:
+        pattern = os.path.join("coverage", pattern)
+        for filename in glob.glob(pattern):
+            try:
+                os.remove(filename)
+            except OSError:
+                pass
+
+def run_tests(tracer, *nose_args):
+    """The actual running of tests."""
+    import nose.core
+    if tracer == "py":
+        label = "with Python tracer"
+    else:
+        label = "with C tracer"
+        if os.environ.get("COVERAGE_NO_EXTENSION"):
+            print("Skipping tests, no C extension in this environment")
+            return
+    print_banner(label)
+    os.environ["COVERAGE_TEST_TRACER"] = tracer
+    nose_args = ["nosetests"] + list(nose_args)
+    nose.core.main(argv=nose_args)
+
+def run_tests_with_coverage(tracer, *nose_args):
+    """Run tests, but with coverage."""
+    import coverage
+
+    os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini')
+    os.environ['COVERAGE_HOME'] = os.getcwd()
+
+    # Create the .pth file that will let us measure coverage in sub-processes.
+    import nose
+    pth_dir = os.path.dirname(os.path.dirname(nose.__file__))
+    pth_path = os.path.join(pth_dir, "covcov.pth")
+    pth_file = open(pth_path, "w")
+    try:
+        pth_file.write("import coverage; coverage.process_startup()\n")
+    finally:
+        pth_file.close()
+
+    version = "%s%s" % sys.version_info[:2]
+    suffix = "%s_%s_%s" % (version, tracer, socket.gethostname())
+
+    cov = coverage.coverage(config_file="metacov.ini", data_suffix=suffix)
+    # Cheap trick: the coverage code itself is excluded from measurement, but
+    # if we clobber the cover_prefix in the coverage object, we can defeat the
+    # self-detection.
+    cov.cover_prefix = "Please measure coverage.py!"
+    cov.erase()
+    cov.start()
+
+    try:
+        # Re-import coverage to get it coverage tested!  I don't understand all
+        # the mechanics here, but if I don't carry over the imported modules
+        # (in covmods), then things go haywire (os == None, eventually).
+        covmods = {}
+        covdir = os.path.split(coverage.__file__)[0]
+        # We have to make a list since we'll be deleting in the loop.
+        modules = list(sys.modules.items())
+        for name, mod in modules:
+            if name.startswith('coverage'):
+                if getattr(mod, '__file__', "??").startswith(covdir):
+                    covmods[name] = mod
+                    del sys.modules[name]
+        import coverage     # don't warn about re-import: pylint: disable=W0404
+        sys.modules.update(covmods)
+
+        # Run nosetests, with the arguments from our command line.
+        try:
+            run_tests(tracer, *nose_args)
+        except SystemExit:
+            # nose3 seems to raise SystemExit, not sure why?
+            pass
+    finally:
+        cov.stop()
+        os.remove(pth_path)
+
+    cov.save()
+
+def do_combine_html():
+    """Combine data from a meta-coverage run, and make the HTML report."""
+    import coverage
+    os.environ['COVERAGE_HOME'] = os.getcwd()
+    cov = coverage.coverage(config_file="metacov.ini")
+    cov.load()
+    cov.combine()
+    cov.save()
+    cov.html_report()
+
+def do_test_with_tracer(tracer, *noseargs):
+    """Run nosetests with a particular tracer."""
+    if os.environ.get("COVERAGE_COVERAGE", ""):
+        return run_tests_with_coverage(tracer, *noseargs)
+    else:
+        return run_tests(tracer, *noseargs)
+
+def do_zip_mods():
+    """Build the zipmods.zip file."""
+    zf = zipfile.ZipFile("test/zipmods.zip", "w")
+    zf.write("test/covmodzip1.py", "covmodzip1.py")
+    zf.close()
+
+def do_install_egg():
+    """Install the egg1 egg for tests."""
+    # I am pretty certain there are easier ways to install eggs...
+    # pylint: disable=F0401,E0611,E1101
+    import distutils.core
+    cur_dir = os.getcwd()
+    os.chdir("test/eggsrc")
+    distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"])
+    egg = glob.glob("dist/*.egg")[0]
+    distutils.core.run_setup(
+        "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg]
+        )
+    os.chdir(cur_dir)
+
+def do_check_eol():
+    """Check files for incorrect newlines and trailing whitespace."""
+
+    ignore_dirs = [
+        '.svn', '.hg', '.tox', '.tox_kits', 'coverage.egg-info',
+        '_build', 'covtestegg1.egg-info',
+        ]
+    checked = set([])
+
+    def check_file(fname, crlf=True, trail_white=True):
+        """Check a single file for whitespace abuse."""
+        fname = os.path.relpath(fname)
+        if fname in checked:
+            return
+        checked.add(fname)
+
+        line = None
+        for n, line in enumerate(open(fname, "rb")):
+            if crlf:
+                if "\r" in line:
+                    print("%s@%d: CR found" % (fname, n+1))
+                    return
+            if trail_white:
+                line = line[:-1]
+                if not crlf:
+                    line = line.rstrip('\r')
+                if line.rstrip() != line:
+                    print("%s@%d: trailing whitespace found" % (fname, n+1))
+                    return
+
+        if line is not None and not line.strip():
+            print("%s: final blank line" % (fname,))
+
+    def check_files(root, patterns, **kwargs):
+        """Check a number of files for whitespace abuse."""
+        for root, dirs, files in os.walk(root):
+            for f in files:
+                fname = os.path.join(root, f)
+                for p in patterns:
+                    if fnmatch.fnmatch(fname, p):
+                        check_file(fname, **kwargs)
+                        break
+            for dir_name in ignore_dirs:
+                if dir_name in dirs:
+                    dirs.remove(dir_name)
+
+    check_files("coverage", ["*.py", "*.c"])
+    check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"])
+    check_file("test/farm/html/src/bom.py", crlf=False)
+    check_files("test", ["*.py"])
+    check_files("test", ["*,cover"], trail_white=False)
+    check_files("test/js", ["*.js", "*.html"])
+    check_file("setup.py")
+    check_file("igor.py")
+    check_files("doc", ["*.rst"])
+    check_files(".", ["*.txt"])
+
+
+def print_banner(label):
+    """Print the version of Python."""
+    try:
+        impl = platform.python_implementation()
+    except AttributeError:
+        impl = "Python"
+
+    version = platform.python_version()
+
+    if '__pypy__' in sys.builtin_module_names:
+        pypy_version = sys.pypy_version_info         # pylint: disable=E1101
+        version += " (pypy %s)" % ".".join([str(v) for v in pypy_version])
+
+    print('=== %s %s %s (%s) ===' % (impl, version, label, sys.executable))
+
+
+def do_help():
+    """List the available commands"""
+    items = globals().items()
+    items.sort()
+    for name, value in items:
+        if name.startswith('do_'):
+            print("%-20s%s" % (name[3:], value.__doc__))
+
+
+def main(args):
+    """Main command-line execution for igor.
+
+    Verbs are taken from the command line, and extra words taken as directed
+    by the arguments needed by the handler.
+
+    """
+    while args:
+        verb = args.pop(0)
+        handler = globals().get('do_'+verb)
+        if handler is None:
+            print("*** No handler for %r" % verb)
+            return 1
+        argspec = inspect.getargspec(handler)
+        if argspec[1]:
+            # Handler has *args, give it all the rest of the command line.
+            handler_args = args
+            args = []
+        else:
+            # Handler has specific arguments, give it only what it needs.
+            num_args = len(argspec[0])
+            handler_args = args[:num_args]
+            args = args[num_args:]
+        ret = handler(*handler_args)
+        # If a handler returns a failure-like value, stop.
+        if ret:
+            return ret
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..4c66774
--- /dev/null
@@ -0,0 +1,5 @@
+nose
+mock
+tox
+pylint
+sphinx
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..b14b0bc
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]\r
+tag_build = \r
+tag_date = 0\r
+tag_svn_revision = 0\r
+\r
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..0b48843
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,199 @@
+# setup.py for coverage.py
+
+"""Code coverage measurement for Python
+
+Coverage.py measures code coverage, typically during test execution. It uses
+the code analysis tools and tracing hooks provided in the Python standard
+library to determine which lines are executable, and which have been executed.
+
+Coverage.py runs on Pythons 2.3 through 3.3, and PyPy 1.9.
+
+Documentation is at `nedbatchelder.com <%s>`_.  Code repository and issue
+tracker are at `bitbucket.org <http://bitbucket.org/ned/coveragepy>`_.
+
+New in 3.6: ``--fail-under``, and >20 bugs closed.
+
+New in 3.5: Branch coverage exclusions, keyboard shortcuts in HTML report.
+
+New in 3.4: Better control over source to measure, and unexecuted files
+can be reported.
+
+New in 3.3: .coveragerc files.
+
+New in 3.2: Branch coverage!
+"""
+
+# This file is used unchanged under all versions of Python, 2.x and 3.x.
+
+classifiers = """\
+Environment :: Console
+Intended Audience :: Developers
+License :: OSI Approved :: BSD License
+Operating System :: OS Independent
+Programming Language :: Python :: 2
+Programming Language :: Python :: 3
+Topic :: Software Development :: Quality Assurance
+Topic :: Software Development :: Testing
+"""
+
+# Pull in the tools we need.
+import os, sys
+
+from setuptools import setup
+from distutils.core import Extension        # pylint: disable=E0611,F0401
+from distutils.command.build_ext import build_ext   # pylint: disable=E0611,F0401,C0301
+from distutils import errors                # pylint: disable=E0611,F0401
+
+# Get or massage our metadata.  We exec coverage/version.py so we can avoid
+# importing the product code into setup.py.
+
+doc = __doc__                   # __doc__ will be overwritten by version.py.
+__version__ = __url__ = ""      # Keep pylint happy.
+
+cov_ver_py = os.path.join(os.path.split(__file__)[0], "coverage/version.py")
+version_file = open(cov_ver_py)
+try:
+    exec(compile(version_file.read(), cov_ver_py, 'exec'))
+finally:
+    version_file.close()
+
+doclines = (doc % __url__).splitlines()
+classifier_list = classifiers.splitlines()
+
+if 'a' in __version__:
+    devstat = "3 - Alpha"
+elif 'b' in __version__:
+    devstat = "4 - Beta"
+else:
+    devstat = "5 - Production/Stable"
+classifier_list.append("Development Status :: " + devstat)
+
+# Install a script as "coverage", and as "coverage[23]", and as
+# "coverage-2.7" (or whatever).
+scripts = [
+    'coverage = coverage:main',
+    'coverage%d = coverage:main' % sys.version_info[:1],
+    'coverage-%d.%d = coverage:main' % sys.version_info[:2],
+    ]
+
+# Create the keyword arguments for setup()
+
+setup_args = dict(
+    name = 'coverage',
+    version = __version__,
+
+    packages = [
+        'coverage',
+        ],
+
+    package_data = {
+        'coverage': [
+            'htmlfiles/*.*',
+            ]
+        },
+
+    entry_points = {'console_scripts': scripts},
+
+    # We need to get HTML assets from our htmlfiles dir.
+    zip_safe = False,
+
+    author = 'Ned Batchelder and others',
+    author_email = 'ned@nedbatchelder.com',
+    description = doclines[0],
+    long_description = '\n'.join(doclines[2:]),
+    keywords = 'code coverage testing',
+    license = 'BSD',
+    classifiers = classifier_list,
+    url = __url__,
+    )
+
+# A replacement for the build_ext command which raises a single exception
+# if the build fails, so we can fallback nicely.
+
+ext_errors = (
+    errors.CCompilerError,
+    errors.DistutilsExecError,
+    errors.DistutilsPlatformError,
+)
+if sys.platform == 'win32' and sys.version_info > (2, 6):
+    # 2.6's distutils.msvc9compiler can raise an IOError when failing to
+    # find the compiler
+    ext_errors += (IOError,)
+
+class BuildFailed(Exception):
+    """Raise this to indicate the C extension wouldn't build."""
+    def __init__(self):
+        Exception.__init__(self)
+        self.cause = sys.exc_info()[1] # work around py 2/3 different syntax
+
+class ve_build_ext(build_ext):
+    """Build C extensions, but fail with a straightforward exception."""
+
+    def run(self):
+        """Wrap `run` with `BuildFailed`."""
+        try:
+            build_ext.run(self)
+        except errors.DistutilsPlatformError:
+            raise BuildFailed()
+
+    def build_extension(self, ext):
+        """Wrap `build_extension` with `BuildFailed`."""
+        try:
+            # Uncomment to test compile failures:
+            #   raise errors.CCompilerError("OOPS")
+            build_ext.build_extension(self, ext)
+        except ext_errors:
+            raise BuildFailed()
+        except ValueError:
+            # this can happen on Windows 64 bit, see Python issue 7511
+            if "'path'" in str(sys.exc_info()[1]): # works with both py 2/3
+                raise BuildFailed()
+            raise
+
+# There are a few reasons we might not be able to compile the C extension.
+# Figure out if we should attempt the C extension or not.
+
+compile_extension = True
+
+if sys.platform.startswith('java'):
+    # Jython can't compile C extensions
+    compile_extension = False
+
+if '__pypy__' in sys.builtin_module_names:
+    # Pypy can't compile C extensions
+    compile_extension = False
+
+if compile_extension:
+    setup_args.update(dict(
+        ext_modules = [
+            Extension("coverage.tracer", sources=["coverage/tracer.c"])
+            ],
+        cmdclass = {
+            'build_ext': ve_build_ext,
+            },
+        ))
+
+# Py3.x-specific details.
+
+if sys.version_info >= (3, 0):
+    setup_args.update(dict(
+        use_2to3 = False,
+        ))
+
+def main():
+    """Actually invoke setup() with the arguments we built above."""
+    # For a variety of reasons, it might not be possible to install the C
+    # extension.  Try it with, and if it fails, try it without.
+    try:
+        setup(**setup_args)
+    except BuildFailed:
+        msg = "Couldn't install with extension module, trying without it..."
+        exc = sys.exc_info()[1]
+        exc_msg = "%s: %s" % (exc.__class__.__name__, exc.cause)
+        print("**\n** %s\n** %s\n**" % (msg, exc_msg))
+
+        del setup_args['ext_modules']
+        setup(**setup_args)
+
+if __name__ == '__main__':
+    main()
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644 (file)
index 0000000..5a0e30f
--- /dev/null
@@ -0,0 +1 @@
+"""Automated tests. Run with nosetests."""
diff --git a/test/backtest.py b/test/backtest.py
new file mode 100644 (file)
index 0000000..b17aa24
--- /dev/null
@@ -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/test/backunittest.py b/test/backunittest.py
new file mode 100644 (file)
index 0000000..30da78e
--- /dev/null
@@ -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/test/coveragetest.py b/test/coveragetest.py
new file mode 100644 (file)
index 0000000..6f6217a
--- /dev/null
@@ -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/test/covmodzip1.py b/test/covmodzip1.py
new file mode 100644 (file)
index 0000000..3ec4cdc
--- /dev/null
@@ -0,0 +1,3 @@
+"""covmodzip.py: for putting into a zip file."""
+j = 1
+j += 1
diff --git a/test/eggsrc/egg1/__init__.py b/test/eggsrc/egg1/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/eggsrc/egg1/egg1.py b/test/eggsrc/egg1/egg1.py
new file mode 100644 (file)
index 0000000..3fadde3
--- /dev/null
@@ -0,0 +1,4 @@
+# My egg file!
+
+walrus = "Eggman"
+says = "coo-coo cachoo"
diff --git a/test/eggsrc/setup.py b/test/eggsrc/setup.py
new file mode 100644 (file)
index 0000000..f9b8b9d
--- /dev/null
@@ -0,0 +1,8 @@
+from setuptools import setup
+
+setup(
+    name="covtestegg1",
+    packages=['egg1'],
+    zip_safe=True,
+    install_requires=[],
+    )
diff --git a/test/farm/annotate/annotate_dir.py b/test/farm/annotate/annotate_dir.py
new file mode 100644 (file)
index 0000000..3e37f9e
--- /dev/null
@@ -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/test/farm/annotate/gold/white.py,cover b/test/farm/annotate/gold/white.py,cover
new file mode 100644 (file)
index 0000000..36b0b99
--- /dev/null
@@ -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/test/farm/annotate/gold_anno_dir/a___init__.py,cover b/test/farm/annotate/gold_anno_dir/a___init__.py,cover
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/farm/annotate/gold_anno_dir/a_a.py,cover b/test/farm/annotate/gold_anno_dir/a_a.py,cover
new file mode 100644 (file)
index 0000000..d0ff3c0
--- /dev/null
@@ -0,0 +1,5 @@
+> def a(x):
+>     if x == 1:
+>         print("x is 1")
+!     else:
+!         print("x is not 1")
diff --git a/test/farm/annotate/gold_anno_dir/b___init__.py,cover b/test/farm/annotate/gold_anno_dir/b___init__.py,cover
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/farm/annotate/gold_anno_dir/b_b.py,cover b/test/farm/annotate/gold_anno_dir/b_b.py,cover
new file mode 100644 (file)
index 0000000..90d076f
--- /dev/null
@@ -0,0 +1,3 @@
+> def b(x):
+>     msg = "x is %s" % x
+>     print(msg)
diff --git a/test/farm/annotate/gold_anno_dir/multi.py,cover b/test/farm/annotate/gold_anno_dir/multi.py,cover
new file mode 100644 (file)
index 0000000..2a5c59c
--- /dev/null
@@ -0,0 +1,5 @@
+> import a.a
+> import b.b
+  
+> a.a.a(1)
+> b.b.b(2)
diff --git a/test/farm/annotate/gold_multi/a/__init__.py,cover b/test/farm/annotate/gold_multi/a/__init__.py,cover
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/farm/annotate/gold_multi/a/a.py,cover b/test/farm/annotate/gold_multi/a/a.py,cover
new file mode 100644 (file)
index 0000000..fb3f543
--- /dev/null
@@ -0,0 +1,5 @@
+> def a(x):
+>     if x == 1:
+>         print "x is 1"
+!     else:
+!         print "x is not 1"
diff --git a/test/farm/annotate/gold_multi/b/__init__.py,cover b/test/farm/annotate/gold_multi/b/__init__.py,cover
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/farm/annotate/gold_multi/b/b.py,cover b/test/farm/annotate/gold_multi/b/b.py,cover
new file mode 100644 (file)
index 0000000..a3f5dae
--- /dev/null
@@ -0,0 +1,2 @@
+> def b(x):
+>     print "x is %s" % x
diff --git a/test/farm/annotate/gold_multi/multi.py,cover b/test/farm/annotate/gold_multi/multi.py,cover
new file mode 100644 (file)
index 0000000..2a5c59c
--- /dev/null
@@ -0,0 +1,5 @@
+> import a.a
+> import b.b
+  
+> a.a.a(1)
+> b.b.b(2)
diff --git a/test/farm/annotate/gold_v24/white.py,cover b/test/farm/annotate/gold_v24/white.py,cover
new file mode 100644 (file)
index 0000000..bbd8d42
--- /dev/null
@@ -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/test/farm/annotate/run.py b/test/farm/annotate/run.py
new file mode 100644 (file)
index 0000000..c645f21
--- /dev/null
@@ -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/test/farm/annotate/run_multi.py b/test/farm/annotate/run_multi.py
new file mode 100644 (file)
index 0000000..4e8252e
--- /dev/null
@@ -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/test/farm/annotate/src/a/__init__.py b/test/farm/annotate/src/a/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/farm/annotate/src/a/a.py b/test/farm/annotate/src/a/a.py
new file mode 100644 (file)
index 0000000..c2583d1
--- /dev/null
@@ -0,0 +1,5 @@
+def a(x):
+    if x == 1:
+        print("x is 1")
+    else:
+        print("x is not 1")
diff --git a/test/farm/annotate/src/b/__init__.py b/test/farm/annotate/src/b/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/farm/annotate/src/b/b.py b/test/farm/annotate/src/b/b.py
new file mode 100644 (file)
index 0000000..625a549
--- /dev/null
@@ -0,0 +1,3 @@
+def b(x):
+    msg = "x is %s" % x
+    print(msg)
diff --git a/test/farm/annotate/src/multi.py b/test/farm/annotate/src/multi.py
new file mode 100644 (file)
index 0000000..19a6200
--- /dev/null
@@ -0,0 +1,5 @@
+import a.a
+import b.b
+
+a.a.a(1)
+b.b.b(2)
diff --git a/test/farm/annotate/src/white.py b/test/farm/annotate/src/white.py
new file mode 100644 (file)
index 0000000..ecbbd25
--- /dev/null
@@ -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/test/farm/html/gold_a/a.html b/test/farm/html/gold_a/a.html
new file mode 100644 (file)
index 0000000..c794525
--- /dev/null
@@ -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/test/farm/html/gold_a/index.html b/test/farm/html/gold_a/index.html
new file mode 100644 (file)
index 0000000..a821e9d
--- /dev/null
@@ -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/test/farm/html/gold_b_branch/b.html b/test/farm/html/gold_b_branch/b.html
new file mode 100644 (file)
index 0000000..0258ad1
--- /dev/null
@@ -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/test/farm/html/gold_b_branch/index.html b/test/farm/html/gold_b_branch/index.html
new file mode 100644 (file)
index 0000000..cb6ffa1
--- /dev/null
@@ -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/test/farm/html/gold_bom/bom.html b/test/farm/html/gold_bom/bom.html
new file mode 100644 (file)
index 0000000..1d61a62
--- /dev/null
@@ -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/test/farm/html/gold_bom/index.html b/test/farm/html/gold_bom/index.html
new file mode 100644 (file)
index 0000000..8653b23
--- /dev/null
@@ -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/test/farm/html/gold_isolatin1/index.html b/test/farm/html/gold_isolatin1/index.html
new file mode 100644 (file)
index 0000000..6e9f3ca
--- /dev/null
@@ -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/test/farm/html/gold_isolatin1/isolatin1.html b/test/farm/html/gold_isolatin1/isolatin1.html
new file mode 100644 (file)
index 0000000..276a6c2
--- /dev/null
@@ -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/test/farm/html/gold_omit_1/index.html b/test/farm/html/gold_omit_1/index.html
new file mode 100644 (file)
index 0000000..5616d01
--- /dev/null
@@ -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/test/farm/html/gold_omit_1/m1.html b/test/farm/html/gold_omit_1/m1.html
new file mode 100644 (file)
index 0000000..62ba1e0
--- /dev/null
@@ -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/test/farm/html/gold_omit_1/m2.html b/test/farm/html/gold_omit_1/m2.html
new file mode 100644 (file)
index 0000000..d75a5ba
--- /dev/null
@@ -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/test/farm/html/gold_omit_1/m3.html b/test/farm/html/gold_omit_1/m3.html
new file mode 100644 (file)
index 0000000..bd99138
--- /dev/null
@@ -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/test/farm/html/gold_omit_1/main.html b/test/farm/html/gold_omit_1/main.html
new file mode 100644 (file)
index 0000000..0394871
--- /dev/null
@@ -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/test/farm/html/gold_omit_2/index.html b/test/farm/html/gold_omit_2/index.html
new file mode 100644 (file)
index 0000000..3ce5bad
--- /dev/null
@@ -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/test/farm/html/gold_omit_2/m2.html b/test/farm/html/gold_omit_2/m2.html
new file mode 100644 (file)
index 0000000..d75a5ba
--- /dev/null
@@ -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/test/farm/html/gold_omit_2/m3.html b/test/farm/html/gold_omit_2/m3.html
new file mode 100644 (file)
index 0000000..bd99138
--- /dev/null
@@ -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/test/farm/html/gold_omit_2/main.html b/test/farm/html/gold_omit_2/main.html
new file mode 100644 (file)
index 0000000..0394871
--- /dev/null
@@ -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/test/farm/html/gold_omit_3/index.html b/test/farm/html/gold_omit_3/index.html
new file mode 100644 (file)
index 0000000..fb826bf
--- /dev/null
@@ -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/test/farm/html/gold_omit_3/m3.html b/test/farm/html/gold_omit_3/m3.html
new file mode 100644 (file)
index 0000000..bd99138
--- /dev/null
@@ -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/test/farm/html/gold_omit_3/main.html b/test/farm/html/gold_omit_3/main.html
new file mode 100644 (file)
index 0000000..0394871
--- /dev/null
@@ -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/test/farm/html/gold_omit_4/index.html b/test/farm/html/gold_omit_4/index.html
new file mode 100644 (file)
index 0000000..e437cf1
--- /dev/null
@@ -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/test/farm/html/gold_omit_4/m1.html b/test/farm/html/gold_omit_4/m1.html
new file mode 100644 (file)
index 0000000..62ba1e0
--- /dev/null
@@ -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/test/farm/html/gold_omit_4/m3.html b/test/farm/html/gold_omit_4/m3.html
new file mode 100644 (file)
index 0000000..bd99138
--- /dev/null
@@ -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/test/farm/html/gold_omit_4/main.html b/test/farm/html/gold_omit_4/main.html
new file mode 100644 (file)
index 0000000..0394871
--- /dev/null
@@ -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/test/farm/html/gold_omit_5/index.html b/test/farm/html/gold_omit_5/index.html
new file mode 100644 (file)
index 0000000..4bde6b7
--- /dev/null
@@ -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/test/farm/html/gold_omit_5/m1.html b/test/farm/html/gold_omit_5/m1.html
new file mode 100644 (file)
index 0000000..62ba1e0
--- /dev/null
@@ -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/test/farm/html/gold_omit_5/main.html b/test/farm/html/gold_omit_5/main.html
new file mode 100644 (file)
index 0000000..0394871
--- /dev/null
@@ -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/test/farm/html/gold_other/blah_blah_other.html b/test/farm/html/gold_other/blah_blah_other.html
new file mode 100644 (file)
index 0000000..ab5ae37
--- /dev/null
@@ -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/test/farm/html/gold_other/here.html b/test/farm/html/gold_other/here.html
new file mode 100644 (file)
index 0000000..1da5bcd
--- /dev/null
@@ -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/test/farm/html/gold_other/index.html b/test/farm/html/gold_other/index.html
new file mode 100644 (file)
index 0000000..7665cfe
--- /dev/null
@@ -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/test/farm/html/gold_partial/index.html b/test/farm/html/gold_partial/index.html
new file mode 100644 (file)
index 0000000..5556150
--- /dev/null
@@ -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/test/farm/html/gold_partial/partial.html b/test/farm/html/gold_partial/partial.html
new file mode 100644 (file)
index 0000000..b9640ce
--- /dev/null
@@ -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/test/farm/html/gold_styled/a.html b/test/farm/html/gold_styled/a.html
new file mode 100644 (file)
index 0000000..c794525
--- /dev/null
@@ -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/test/farm/html/gold_styled/extra.css b/test/farm/html/gold_styled/extra.css
new file mode 100644 (file)
index 0000000..46c41fc
--- /dev/null
@@ -0,0 +1 @@
+/* Doesn't matter what goes in here, it gets copied. */
diff --git a/test/farm/html/gold_styled/index.html b/test/farm/html/gold_styled/index.html
new file mode 100644 (file)
index 0000000..a821e9d
--- /dev/null
@@ -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/test/farm/html/gold_styled/style.css b/test/farm/html/gold_styled/style.css
new file mode 100644 (file)
index 0000000..c40357b
--- /dev/null
@@ -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/test/farm/html/gold_unicode/index.html b/test/farm/html/gold_unicode/index.html
new file mode 100644 (file)
index 0000000..9ba1bb3
--- /dev/null
@@ -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/test/farm/html/gold_unicode/unicode.html b/test/farm/html/gold_unicode/unicode.html
new file mode 100644 (file)
index 0000000..518a59a
--- /dev/null
@@ -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/test/farm/html/gold_x_xml/coverage.xml b/test/farm/html/gold_x_xml/coverage.xml
new file mode 100644 (file)
index 0000000..912112f
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" ?>\r
+<!DOCTYPE coverage\r
+  SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>\r
+<coverage branch-rate="0" line-rate="0.6667" timestamp="1253972570431" version="3.1b1">\r
+       <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage/VER -->\r
+       <packages>\r
+               <package branch-rate="0" complexity="0" line-rate="0.6667" name="">\r
+                       <classes>\r
+                               <class branch-rate="0" complexity="0" filename="a.py" line-rate="0.6667" name="a">\r
+                                       <methods/>\r
+                                       <lines>\r
+                                               <line hits="1" number="3"/>\r
+                                               <line hits="1" number="5"/>\r
+                                               <line hits="0" number="7"/>\r
+                                       </lines>\r
+                               </class>\r
+                       </classes>\r
+               </package>\r
+       </packages>\r
+</coverage>\r
diff --git a/test/farm/html/gold_y_xml_branch/coverage.xml b/test/farm/html/gold_y_xml_branch/coverage.xml
new file mode 100644 (file)
index 0000000..ecbe007
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" ?>\r
+<!DOCTYPE coverage\r
+  SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>\r
+<coverage branch-rate="0.5" line-rate="0.8" timestamp="1259288252325" version="3.2b4">\r
+       <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage/VER -->\r
+       <packages>\r
+               <package branch-rate="0.5" complexity="0" line-rate="0.8" name="">\r
+                       <classes>\r
+                               <class branch-rate="0.5" complexity="0" filename="y.py" line-rate="0.8" name="y">\r
+                                       <methods/>\r
+                                       <lines>\r
+                                               <line hits="1" number="3"/>\r
+                                               <line branch="true" condition-coverage="50% (1/2)" hits="1" number="4"/>\r
+                                               <line hits="1" number="5"/>\r
+                                               <line hits="0" number="7"/>\r
+                                               <line hits="1" number="9"/>\r
+                                       </lines>\r
+                               </class>\r
+                       </classes>\r
+               </package>\r
+       </packages>\r
+</coverage>\r
diff --git a/test/farm/html/othersrc/other.py b/test/farm/html/othersrc/other.py
new file mode 100644 (file)
index 0000000..6d3f86e
--- /dev/null
@@ -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/test/farm/html/run_a.py b/test/farm/html/run_a.py
new file mode 100644 (file)
index 0000000..59cc170
--- /dev/null
@@ -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/test/farm/html/run_a_xml_1.py b/test/farm/html/run_a_xml_1.py
new file mode 100644 (file)
index 0000000..3d18702
--- /dev/null
@@ -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/test/farm/html/run_a_xml_2.py b/test/farm/html/run_a_xml_2.py
new file mode 100644 (file)
index 0000000..53691ea
--- /dev/null
@@ -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/test/farm/html/run_b_branch.py b/test/farm/html/run_b_branch.py
new file mode 100644 (file)
index 0000000..d0955a2
--- /dev/null
@@ -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/test/farm/html/run_bom.py b/test/farm/html/run_bom.py
new file mode 100644 (file)
index 0000000..c35079b
--- /dev/null
@@ -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/test/farm/html/run_isolatin1.py b/test/farm/html/run_isolatin1.py
new file mode 100644 (file)
index 0000000..063e6e0
--- /dev/null
@@ -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/test/farm/html/run_omit_1.py b/test/farm/html/run_omit_1.py
new file mode 100644 (file)
index 0000000..a5556af
--- /dev/null
@@ -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/test/farm/html/run_omit_2.py b/test/farm/html/run_omit_2.py
new file mode 100644 (file)
index 0000000..19f0ebd
--- /dev/null
@@ -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/test/farm/html/run_omit_3.py b/test/farm/html/run_omit_3.py
new file mode 100644 (file)
index 0000000..87ab8c3
--- /dev/null
@@ -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/test/farm/html/run_omit_4.py b/test/farm/html/run_omit_4.py
new file mode 100644 (file)
index 0000000..ede223f
--- /dev/null
@@ -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/test/farm/html/run_omit_5.py b/test/farm/html/run_omit_5.py
new file mode 100644 (file)
index 0000000..8da5199
--- /dev/null
@@ -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/test/farm/html/run_other.py b/test/farm/html/run_other.py
new file mode 100644 (file)
index 0000000..72bb3ec
--- /dev/null
@@ -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/test/farm/html/run_partial.py b/test/farm/html/run_partial.py
new file mode 100644 (file)
index 0000000..41e6ba9
--- /dev/null
@@ -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/test/farm/html/run_styled.py b/test/farm/html/run_styled.py
new file mode 100644 (file)
index 0000000..ac538ff
--- /dev/null
@@ -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/test/farm/html/run_tabbed.py b/test/farm/html/run_tabbed.py
new file mode 100644 (file)
index 0000000..0e9b527
--- /dev/null
@@ -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/test/farm/html/run_unicode.py b/test/farm/html/run_unicode.py
new file mode 100644 (file)
index 0000000..cef26ee
--- /dev/null
@@ -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/test/farm/html/run_y_xml_branch.py b/test/farm/html/run_y_xml_branch.py
new file mode 100644 (file)
index 0000000..88a2e44
--- /dev/null
@@ -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/test/farm/html/src/a.py b/test/farm/html/src/a.py
new file mode 100644 (file)
index 0000000..9e71aeb
--- /dev/null
@@ -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/test/farm/html/src/b.py b/test/farm/html/src/b.py
new file mode 100644 (file)
index 0000000..3bf73a9
--- /dev/null
@@ -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/test/farm/html/src/bom.py b/test/farm/html/src/bom.py
new file mode 100644 (file)
index 0000000..2db8b71
--- /dev/null
@@ -0,0 +1,11 @@
+# A python source file in utf-8, with BOM\r
+math = "3×4 = 12, ÷2 = 6±0"\r
+
+import sys\r
+\r
+if sys.version_info >= (3, 0):\r
+    assert len(math) == 18\r
+    assert len(math.encode('utf-8')) == 21\r
+else:\r
+    assert len(math) == 21\r
+    assert len(math.decode('utf-8')) == 18\r
diff --git a/test/farm/html/src/coverage.xml b/test/farm/html/src/coverage.xml
new file mode 100644 (file)
index 0000000..128cf75
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" ?>\r
+<!DOCTYPE coverage\r
+  SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>\r
+<coverage branch-rate="0.0" line-rate="0.666666666667" timestamp="1263087779313" version="3.3a1">\r
+       <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->\r
+       <packages>\r
+               <package branch-rate="0.0" complexity="0.0" line-rate="0.666666666667" name="">\r
+                       <classes>\r
+                               <class branch-rate="0.0" complexity="0.0" filename="a.py" line-rate="0.666666666667" name="a">\r
+                                       <methods/>\r
+                                       <lines>\r
+                                               <line hits="1" number="3"/>\r
+                                               <line hits="1" number="5"/>\r
+                                               <line hits="0" number="7"/>\r
+                                       </lines>\r
+                               </class>\r
+                       </classes>\r
+               </package>\r
+       </packages>\r
+</coverage>\r
diff --git a/test/farm/html/src/extra.css b/test/farm/html/src/extra.css
new file mode 100644 (file)
index 0000000..46c41fc
--- /dev/null
@@ -0,0 +1 @@
+/* Doesn't matter what goes in here, it gets copied. */
diff --git a/test/farm/html/src/here.py b/test/farm/html/src/here.py
new file mode 100644 (file)
index 0000000..d0d26ea
--- /dev/null
@@ -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/test/farm/html/src/isolatin1.py b/test/farm/html/src/isolatin1.py
new file mode 100644 (file)
index 0000000..057c097
--- /dev/null
@@ -0,0 +1,5 @@
+# A python source file in another encoding.
+# -*- coding: iso8859-1 -*-
+
+math = "3×4 = 12, ÷2 = 6±0"
+assert len(math) == 18
diff --git a/test/farm/html/src/m1.py b/test/farm/html/src/m1.py
new file mode 100644 (file)
index 0000000..927e1f6
--- /dev/null
@@ -0,0 +1,2 @@
+m1a = 1
+m1b = 2
diff --git a/test/farm/html/src/m2.py b/test/farm/html/src/m2.py
new file mode 100644 (file)
index 0000000..ffddf6c
--- /dev/null
@@ -0,0 +1,2 @@
+m2a = 1
+m2b = 2
diff --git a/test/farm/html/src/m3.py b/test/farm/html/src/m3.py
new file mode 100644 (file)
index 0000000..395d7d2
--- /dev/null
@@ -0,0 +1,2 @@
+m3a = 1
+m3b = 2
diff --git a/test/farm/html/src/main.py b/test/farm/html/src/main.py
new file mode 100644 (file)
index 0000000..ce89446
--- /dev/null
@@ -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/test/farm/html/src/omit4.ini b/test/farm/html/src/omit4.ini
new file mode 100644 (file)
index 0000000..6821ecd
--- /dev/null
@@ -0,0 +1,2 @@
+[report]
+omit = m2.py
diff --git a/test/farm/html/src/omit5.ini b/test/farm/html/src/omit5.ini
new file mode 100644 (file)
index 0000000..7e32b41
--- /dev/null
@@ -0,0 +1,8 @@
+[report]
+omit =
+    fooey
+    gooey, m[23]*, kablooey
+    helloworld
+
+[html]
+directory = ../html_omit_5
diff --git a/test/farm/html/src/partial.py b/test/farm/html/src/partial.py
new file mode 100644 (file)
index 0000000..8d62f5c
--- /dev/null
@@ -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/test/farm/html/src/run_a_xml_2.ini b/test/farm/html/src/run_a_xml_2.ini
new file mode 100644 (file)
index 0000000..8d28f97
--- /dev/null
@@ -0,0 +1,3 @@
+# Put all the XML output in xml_2
+[xml]
+output = ../xml_2/coverage.xml
diff --git a/test/farm/html/src/tabbed.py b/test/farm/html/src/tabbed.py
new file mode 100644 (file)
index 0000000..2035852
--- /dev/null
@@ -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/test/farm/html/src/unicode.py b/test/farm/html/src/unicode.py
new file mode 100644 (file)
index 0000000..f6a9a05
--- /dev/null
@@ -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/test/farm/html/src/y.py b/test/farm/html/src/y.py
new file mode 100644 (file)
index 0000000..af7c968
--- /dev/null
@@ -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/test/farm/run/run_chdir.py b/test/farm/run/run_chdir.py
new file mode 100644 (file)
index 0000000..f459f50
--- /dev/null
@@ -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/test/farm/run/run_timid.py b/test/farm/run/run_timid.py
new file mode 100644 (file)
index 0000000..ce78fff
--- /dev/null
@@ -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/test/farm/run/run_xxx.py b/test/farm/run/run_xxx.py
new file mode 100644 (file)
index 0000000..19e94a4
--- /dev/null
@@ -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/test/farm/run/src/chdir.py b/test/farm/run/src/chdir.py
new file mode 100644 (file)
index 0000000..6d83492
--- /dev/null
@@ -0,0 +1,4 @@
+import os
+print("Line One")
+os.chdir("subdir")
+print("Line Two")
diff --git a/test/farm/run/src/showtrace.py b/test/farm/run/src/showtrace.py
new file mode 100644 (file)
index 0000000..e97412e
--- /dev/null
@@ -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/test/farm/run/src/subdir/placeholder b/test/farm/run/src/subdir/placeholder
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/farm/run/src/xxx b/test/farm/run/src/xxx
new file mode 100644 (file)
index 0000000..8f727f0
--- /dev/null
@@ -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/test/js/index.html b/test/js/index.html
new file mode 100644 (file)
index 0000000..60bdb30
--- /dev/null
@@ -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/test/js/tests.js b/test/js/tests.js
new file mode 100644 (file)
index 0000000..73b4ce2
--- /dev/null
@@ -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/test/modules/aa/__init__.py b/test/modules/aa/__init__.py
new file mode 100644 (file)
index 0000000..77593d8
--- /dev/null
@@ -0,0 +1 @@
+# aa
diff --git a/test/modules/aa/afile.odd.py b/test/modules/aa/afile.odd.py
new file mode 100644 (file)
index 0000000..c6f49e1
--- /dev/null
@@ -0,0 +1 @@
+# afile.odd.py
diff --git a/test/modules/aa/afile.py b/test/modules/aa/afile.py
new file mode 100644 (file)
index 0000000..3f0e38d
--- /dev/null
@@ -0,0 +1 @@
+# afile.py
diff --git a/test/modules/aa/bb.odd/bfile.py b/test/modules/aa/bb.odd/bfile.py
new file mode 100644 (file)
index 0000000..9087540
--- /dev/null
@@ -0,0 +1 @@
+# bfile.py
diff --git a/test/modules/aa/bb/__init__.py b/test/modules/aa/bb/__init__.py
new file mode 100644 (file)
index 0000000..ffbe624
--- /dev/null
@@ -0,0 +1 @@
+# bb
diff --git a/test/modules/aa/bb/bfile.odd.py b/test/modules/aa/bb/bfile.odd.py
new file mode 100644 (file)
index 0000000..b45cba8
--- /dev/null
@@ -0,0 +1 @@
+# bfile.odd.py
diff --git a/test/modules/aa/bb/bfile.py b/test/modules/aa/bb/bfile.py
new file mode 100644 (file)
index 0000000..9087540
--- /dev/null
@@ -0,0 +1 @@
+# bfile.py
diff --git a/test/modules/aa/bb/cc/__init__.py b/test/modules/aa/bb/cc/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/modules/aa/bb/cc/cfile.py b/test/modules/aa/bb/cc/cfile.py
new file mode 100644 (file)
index 0000000..7976475
--- /dev/null
@@ -0,0 +1 @@
+# cfile.py
diff --git a/test/modules/aa/zfile.py b/test/modules/aa/zfile.py
new file mode 100644 (file)
index 0000000..924f9b7
--- /dev/null
@@ -0,0 +1 @@
+# zfile.py
diff --git a/test/modules/covmod1.py b/test/modules/covmod1.py
new file mode 100644 (file)
index 0000000..b3f5e5f
--- /dev/null
@@ -0,0 +1,3 @@
+# covmod1.py: Simplest module for testing.
+i = 1
+i += 1
diff --git a/test/modules/pkg1/__init__.py b/test/modules/pkg1/__init__.py
new file mode 100644 (file)
index 0000000..2dfeb9c
--- /dev/null
@@ -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/test/modules/pkg1/__main__.py b/test/modules/pkg1/__main__.py
new file mode 100644 (file)
index 0000000..66ce595
--- /dev/null
@@ -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/test/modules/pkg1/p1a.py b/test/modules/pkg1/p1a.py
new file mode 100644 (file)
index 0000000..be5fcdd
--- /dev/null
@@ -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/test/modules/pkg1/p1b.py b/test/modules/pkg1/p1b.py
new file mode 100644 (file)
index 0000000..59d6fb5
--- /dev/null
@@ -0,0 +1,3 @@
+x = 1
+y = 2
+z = 3
diff --git a/test/modules/pkg1/p1c.py b/test/modules/pkg1/p1c.py
new file mode 100644 (file)
index 0000000..a9aeef0
--- /dev/null
@@ -0,0 +1,3 @@
+a = 1
+b = 2
+c = 3
diff --git a/test/modules/pkg1/runmod2.py b/test/modules/pkg1/runmod2.py
new file mode 100644 (file)
index 0000000..b52964c
--- /dev/null
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("runmod2: passed %s" % sys.argv[1])
diff --git a/test/modules/pkg1/sub/__init__.py b/test/modules/pkg1/sub/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/modules/pkg1/sub/__main__.py b/test/modules/pkg1/sub/__main__.py
new file mode 100644 (file)
index 0000000..b5be9f1
--- /dev/null
@@ -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/test/modules/pkg1/sub/ps1a.py b/test/modules/pkg1/sub/ps1a.py
new file mode 100644 (file)
index 0000000..4b6a15c
--- /dev/null
@@ -0,0 +1,3 @@
+d = 1
+e = 2
+f = 3
diff --git a/test/modules/pkg1/sub/runmod3.py b/test/modules/pkg1/sub/runmod3.py
new file mode 100644 (file)
index 0000000..3a1ad15
--- /dev/null
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("runmod3: passed %s" % sys.argv[1])
diff --git a/test/modules/pkg2/__init__.py b/test/modules/pkg2/__init__.py
new file mode 100644 (file)
index 0000000..090efbf
--- /dev/null
@@ -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/test/modules/pkg2/p2a.py b/test/modules/pkg2/p2a.py
new file mode 100644 (file)
index 0000000..b606711
--- /dev/null
@@ -0,0 +1,3 @@
+q = 1
+r = 1
+s = 1
diff --git a/test/modules/pkg2/p2b.py b/test/modules/pkg2/p2b.py
new file mode 100644 (file)
index 0000000..7a34e2c
--- /dev/null
@@ -0,0 +1,3 @@
+t = 1
+u = 1
+v = 1
diff --git a/test/modules/runmod1.py b/test/modules/runmod1.py
new file mode 100644 (file)
index 0000000..671d81e
--- /dev/null
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("runmod1: passed %s" % sys.argv[1])
diff --git a/test/modules/usepkgs.py b/test/modules/usepkgs.py
new file mode 100644 (file)
index 0000000..93c7d90
--- /dev/null
@@ -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/test/moremodules/othermods/__init__.py b/test/moremodules/othermods/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/moremodules/othermods/othera.py b/test/moremodules/othermods/othera.py
new file mode 100644 (file)
index 0000000..7889692
--- /dev/null
@@ -0,0 +1,2 @@
+o = 1
+p = 2
diff --git a/test/moremodules/othermods/otherb.py b/test/moremodules/othermods/otherb.py
new file mode 100644 (file)
index 0000000..2bd8a44
--- /dev/null
@@ -0,0 +1,2 @@
+q = 3
+r = 4
diff --git a/test/moremodules/othermods/sub/__init__.py b/test/moremodules/othermods/sub/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/moremodules/othermods/sub/osa.py b/test/moremodules/othermods/sub/osa.py
new file mode 100644 (file)
index 0000000..0139d28
--- /dev/null
@@ -0,0 +1,2 @@
+s = 5
+t = 6
diff --git a/test/moremodules/othermods/sub/osb.py b/test/moremodules/othermods/sub/osb.py
new file mode 100644 (file)
index 0000000..b024b14
--- /dev/null
@@ -0,0 +1,2 @@
+u = 7
+v = 8
diff --git a/test/osinfo.py b/test/osinfo.py
new file mode 100644 (file)
index 0000000..25c3a7c
--- /dev/null
@@ -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/test/qunit/jquery.tmpl.min.js b/test/qunit/jquery.tmpl.min.js
new file mode 100644 (file)
index 0000000..7438b2c
--- /dev/null
@@ -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/test/qunit/qunit.css b/test/qunit/qunit.css
new file mode 100644 (file)
index 0000000..b3c6db5
--- /dev/null
@@ -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/test/qunit/qunit.js b/test/qunit/qunit.js
new file mode 100644 (file)
index 0000000..e00cca9
--- /dev/null
@@ -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/test/stress_phystoken.tok b/test/stress_phystoken.tok
new file mode 100644 (file)
index 0000000..8d1b6be
--- /dev/null
@@ -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/test/stress_phystoken_dos.tok b/test/stress_phystoken_dos.tok
new file mode 100644 (file)
index 0000000..b08fd70
--- /dev/null
@@ -0,0 +1,52 @@
+# Here's some random Python so that test_tokenize_myself will have some\r
+# stressful stuff to try.  This file is .tok instead of .py so pylint won't\r
+# complain about it, check_eol won't look at it, etc.\r
+\r
+first_back = """\\r
+hey there!\r
+"""\r
+\r
+other_back = """\r
+hey \\r
+there\r
+"""\r
+\r
+lots_of_back = """\\r
+hey \\r
+there\r
+"""\r
+# This next line is supposed to have trailing whitespace:\r
+fake_back = """\ \r
+ouch\r
+"""\r
+\r
+# Lots of difficulty happens with code like:\r
+#\r
+# fake_back = """\\r
+# ouch\r
+# """\r
+#\r
+# Ugh, the edge cases...\r
+\r
+# What about a comment like this\\r
+"what's this string doing here?"\r
+\r
+class C(object):\r
+        def there():\r
+                this = 5 + \\r
+                    7\r
+                that = \\r
+                    "a continued line"\r
+\r
+cont1 = "one line of text" + \\r
+    "another line of text"\r
+\r
+a_long_string =              \\r
+    "part 1"                 \\r
+    "2"                      \\r
+    "3 is longer"\r
+\r
+def hello():\r
+        print("Hello world!")\r
+\r
+hello()\r
diff --git a/test/test_api.py b/test/test_api.py
new file mode 100644 (file)
index 0000000..559684b
--- /dev/null
@@ -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/test/test_arcs.py b/test/test_arcs.py
new file mode 100644 (file)
index 0000000..fc16c0f
--- /dev/null
@@ -0,0 +1,571 @@
+"""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("""\
+                fn = lambda x: x
+                a = 1
+                """,
+                arcz=".1 12 2."
+                )
+
+
+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."
+            )
+
+    if 0:   # expected failure
+        def test_confusing_for_loop_bug_175(self):
+            self.check_coverage("""\
+                o = [(1,2), (3,4)]
+                o = [a for a in o if a[0] > 1]
+                for tup in o:
+                    x = tup[0]
+                    y = tup[1]
+                """,
+                arcz=".1 12 23 34 45 53 3.",
+                arcz_missing="", arcz_unpredicted="")
+            self.check_coverage("""\
+                o = [(1,2), (3,4)]
+                for tup in [a for a in o if a[0] > 1]:
+                    x = tup[0]
+                    y = tup[1]
+                """,
+                arcz=".1 12 23 34 42 2.",
+                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 0:   # expected failure
+        def test_finally_in_loop_2(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/test/test_cmdline.py b/test/test_cmdline.py
new file mode 100644 (file)
index 0000000..33f9021
--- /dev/null
@@ -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/test/test_codeunit.py b/test/test_codeunit.py
new file mode 100644 (file)
index 0000000..b4caff8
--- /dev/null
@@ -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/test/test_config.py b/test/test_config.py
new file mode 100644 (file)
index 0000000..1ca6376
--- /dev/null
@@ -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/test/test_coverage.py b/test/test_coverage.py
new file mode 100644 (file)
index 0000000..0918dfe
--- /dev/null
@@ -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/test/test_data.py b/test/test_data.py
new file mode 100644 (file)
index 0000000..9281ccc
--- /dev/null
@@ -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/test/test_execfile.py b/test/test_execfile.py
new file mode 100644 (file)
index 0000000..e7d7041
--- /dev/null
@@ -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/test/test_farm.py b/test/test_farm.py
new file mode 100644 (file)
index 0000000..f25d610
--- /dev/null
@@ -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/test/test_files.py b/test/test_files.py
new file mode 100644 (file)
index 0000000..051b274
--- /dev/null
@@ -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/test/test_html.py b/test/test_html.py
new file mode 100644 (file)
index 0000000..66d1bd7
--- /dev/null
@@ -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/test/test_misc.py b/test/test_misc.py
new file mode 100644 (file)
index 0000000..a32030c
--- /dev/null
@@ -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/test/test_oddball.py b/test/test_oddball.py
new file mode 100644 (file)
index 0000000..113328b
--- /dev/null
@@ -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/test/test_parser.py b/test/test_parser.py
new file mode 100644 (file)
index 0000000..6ccef20
--- /dev/null
@@ -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/test/test_phystokens.py b/test/test_phystokens.py
new file mode 100644 (file)
index 0000000..e4834e4
--- /dev/null
@@ -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/test/test_process.py b/test/test_process.py
new file mode 100644 (file)
index 0000000..bf22cc9
--- /dev/null
@@ -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/test/test_results.py b/test/test_results.py
new file mode 100644 (file)
index 0000000..3caa5a6
--- /dev/null
@@ -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/test/test_summary.py b/test/test_summary.py
new file mode 100644 (file)
index 0000000..5bb903a
--- /dev/null
@@ -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/test/test_templite.py b/test/test_templite.py
new file mode 100644 (file)
index 0000000..0435c54
--- /dev/null
@@ -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/test/test_testing.py b/test/test_testing.py
new file mode 100644 (file)
index 0000000..c2d1453
--- /dev/null
@@ -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/test/test_xml.py b/test/test_xml.py
new file mode 100644 (file)
index 0000000..204b586
--- /dev/null
@@ -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/test/try_execfile.py b/test/try_execfile.py
new file mode 100644 (file)
index 0000000..9bbabd1
--- /dev/null
@@ -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)
diff --git a/test_old.sh b/test_old.sh
new file mode 100644 (file)
index 0000000..72c3b35
--- /dev/null
@@ -0,0 +1,8 @@
+# Steps to prepare and run coverage.py tests, for Pythons < 2.5
+# This should do the same steps as tox.ini
+easy_install nose==1.2.1 mock==0.6.0
+python setup.py --quiet clean develop
+python igor.py zip_mods install_egg remove_extension 
+python igor.py test_with_tracer py
+python setup.py --quiet build_ext --inplace
+python igor.py test_with_tracer c
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..a09ddbb
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,32 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py25, py26, py27, py31, py32, py33, pypy
+
+[testenv]
+commands =
+    {envpython} setup.py --quiet clean develop
+
+    # Create test/zipmods.zip
+    # Install the egg1 egg
+    # Remove the C extension so that we can test the PyTracer
+    {envpython} igor.py zip_mods install_egg remove_extension 
+
+    # Test with the PyTracer
+    {envpython} igor.py test_with_tracer py {posargs}
+
+    # Build the C extension and test with the CTracer
+    {envpython} setup.py --quiet build_ext --inplace
+    {envpython} igor.py test_with_tracer c {posargs}
+
+deps =
+    nose
+    mock
+
+[testenv:pypy]
+# PyPy has no C extensions
+setenv = 
+    COVERAGE_NO_EXTENSION=1