diff options
258 files changed, 4504 insertions, 4102 deletions
diff --git a/.hgignore b/.hgignore deleted file mode 100644 index ce114b13..00000000 --- a/.hgignore +++ /dev/null @@ -1,42 +0,0 @@ -syntax: glob - -# Files that can appear anywhere in the tree. -*.pyc -*.pyo -*$py.class -*.pyd -*.so -*.bak -.coverage -.coverage.* -coverage.xml -.metacov -.metacov.* -*.swp - -# Stuff generated by editors. -.idea/ -.vimtags - -# Stuff in the root. -build -*.egg-info -dist -htmlcov -MANIFEST -setuptools-*.egg -.tox* -.noseids -.cache -.hypothesis - -# Stuff in the test directory. -zipmods.zip - -# Stuff in the doc directory. -_build -_spell -sample_html_beta - -# Stuff in the ci directory. -*.token diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 8f9ad85c..00000000 --- a/.hgtags +++ /dev/null @@ -1,67 +0,0 @@ -4105a4de000ef94bb1f80cecae7c93aaf282bdbc coverage-3.0b1 -79dd373074def79b3822ee9254307295bf991d16 coverage-3.0b2 -ad991769ad4f685089bacc594e689bab50a6db90 coverage-3.0b3 -9041c4e99c3f8181d35e47df6fd83ceedb1f47fc coverage-3.0 -483a54854358881c360c6a717709b45a5225bd69 coverage-3.0.1 -67f7698c6c0413cc335ed5af7dd981bcf0a126a4 coverage-3.1b1 -67f7698c6c0413cc335ed5af7dd981bcf0a126a4 coverage-3.1b1 -27b6be608b2be50b92b91610846117ab7c47831c coverage-3.1b1 -4d89e089a8f3964522b19d50b4e90be0f2e49b53 coverage-3.1 -524f15a1a176be1b9afb737ab1be99c972f8c928 coverage-3.2b1 -524f15a1a176be1b9afb737ab1be99c972f8c928 coverage-3.2b1 -9231251c603ef0d491b01799b3d1da7af54dd083 coverage-3.2b1 -9231251c603ef0d491b01799b3d1da7af54dd083 coverage-3.2b1 -da052544181e335a18b4141b8b7a47e8246aa9e8 coverage-3.2b1 -11f0fbec2bc9b22a5fd8687d8b94338a4a363e6c coverage-3.2b2 -d42ad49a0aaca90867644876948a7bdab0aa9970 coverage-3.2b3 -3e21d5dc7e9d9c64db56d84b245618ad5c1a4743 coverage-3.2b4 -e3a46ca12bda98e81329300994ca7573dc3af86e coverage-3.2 -e3a46ca12bda98e81329300994ca7573dc3af86e coverage-3.2 -2e1628da1ffce31ed5c58bcad657882de282c858 coverage-3.2 -f63624fedea89b2acb361e1d7d6c5375ebced2aa coverage-3.3 -91bf9589714b14ac4a2ab5995635736994b97348 coverage-3.3.1 -7983cc5256eafaa6c6c3cdb05266493b4c1f991a coverage-3.4b1 -fcc4407bece6ca1266c91734bfdc657b66e10290 coverage-3.4b2 -25f2c1579f1c9539b2c140b86f6c00cbfbf4c565 coverage-3.4 -b7db1026817a4ae98c58c561f6ef79e95d0e13f9 coverage-3.5b1 -7be6416f60b90b75d8c81e5369612b7f48dc26cd coverage-3.5 -9229ab7b00f9e6542b44e788916f4117bd27cfdb coverage-3.5.1b1 -465bedd8af1f79a61f6dda018f6aefe97a79ae4d coverage-3.5.1 -afc3c2ffdb323bf059f136c4c88199899ed93d36 coverage-3.5.2b1 -4eb95ddb88496eccf5f399ba69ca1769baf29dfd coverage-3.5.2 -5429bbb25e8f124f5401cce1effa3089dd792711 coverage-3.5.3 -3483c970350aedb2c8fbe33def962041628f0ba6 coverage-3.6b1 -44d084b18bf0b96f49964df344c1dcaee3802d2e coverage-3.6b2 -773375910ceaaebfce4b5badc584559bf139c862 coverage-3.6b3 -af20c6543c226fbc9deeba4a6d4114641374734a coverage-3.6 -092c0be6d011b6abf85b11476e3b548ed4d5b4c3 coverage-3.7 -41932e001f215955dff895a1fd2fe4133abb89dc coverage-3.7.1 -175fd36ea47ea668066965c80343e1de34f2a94a coverage-4.0a1 -3b97c7c3178f0b88f1ee98f4c58ad00ca3d8e3b4 coverage-4.0a2 -eec8e928880df1beafdf7d4bea87f784375b35d7 coverage-4.0a3 -989f42e9eb11b5864e5746cea675d95ff4cf645d coverage-4.0a4 -00a61f028fcb000ae2cbc77bfbe0ac4cfacfab65 coverage-4.0a5 -90debbdf56495e6c0422ceb5f53f8550d2ad86bf coverage-4.0a6 -825fb6db681e9cfb227f09adbee50b881a3380e2 coverage-4.0b1 -9ab0815227d29c03775f7f7ad6dba1b0d93db777 coverage-4.0b2 -82b0c5a85a7eb2156eebaa6b81b1f62fb4fe51b5 coverage-4.0b3 -3c3e507a247eb35251083b9528a99e50831c960f coverage-4.0 -8e727dc12de10fb8a302b04a8f2af3e00587889e coverage-4.0.1 -7428dab9307da4660878436fe71b696ca2048cf2 coverage-4.0.2 -5ab728b8fd55bd78352012a15a6541fa73dd9e2c coverage-4.0.3 -166fbccc94fb32e52a18508c5422ddae5d5184b9 coverage-4.1b1 -05377ccfe33a83068962a9010037db2d3ebdfa1a coverage-4.1b2 -4984b5b2273622a98d6e4b7de6461bbf6fae41e3 coverage-4.1b3 -1b57373355d1d0a2de258655c47412698d5f061a coverage-4.1 -c84cdae01ca45f85e396c65c20e0234e89ef1785 coverage-4.2b1 -d96ff67e4ef7d0c5e787ed5aafbc1b5a04f6e97a coverage-4.2 -e3e18c651fa059c909c25992d5b7a6db371de09b coverage-4.3 -6ecabe2f7a8d8676a05585bffe19f88b195698ae coverage-4.3.1 -dd2d866194d2eca05862230e6003c6e04fc2fdc0 coverage-4.3.2 -3714d7c42000deafd2fb2034fc8eee6a9ec4c280 coverage-4.3.3 -19d0627fca7a7e76c3c27df1d70ce7f536f1ff23 coverage-4.3.4 -30ab67f19960fafd836c89a08b5f7ee1510b8c03 coverage-4.4b1 -8ad6fa0931cd5eb9b7bb8e6dec6b066d2988fc8f coverage-4.4 -ed196840b79136f17ab493699ec83dcf7dbfe973 coverage-4.4.1 -b65ae46a6504b8d577e967bd3fdcfcaceec95528 coverage-4.4.2 -102b2250a123537e640cd014f5df281822e79cec coverage-4.5 diff --git a/.travis.yml b/.travis.yml index 5e28d48b..c2840e29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,30 @@ # Tell Travis what to do -# https://travis-ci.org/nedbat/coveragepy +# https://travis-ci.com/nedbat/coveragepy language: python +cache: pip sudo: false python: - - '2.6' - '2.7' - - '3.3' - '3.4' - '3.5' - '3.6' - 'pypy' + - 'pypy3.5' env: matrix: - COVERAGE_COVERAGE=no - COVERAGE_COVERAGE=yes -sudo: false - install: - pip install -r requirements/ci.pip + - pip freeze script: - tox + - pip freeze - if [[ $COVERAGE_COVERAGE == 'yes' ]]; then python igor.py combine_html; fi - if [[ $COVERAGE_COVERAGE == 'yes' ]]; then pip install codecov; fi - if [[ $COVERAGE_COVERAGE == 'yes' ]]; then codecov -X gcov --file coverage.xml; fi diff --git a/CHANGES.rst b/CHANGES.rst index e4243b9b..b449797b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt ============================== Change history for Coverage.py @@ -11,10 +11,98 @@ Change history for Coverage.py .. .. .. _changes_781: .. - .. .. Version 7.8.1 --- 2021-07-27 .. ---------------------------- +Unreleased +---------- + +- Context support: static contexts let you specify a label for a coverage run, + which is recorded in the data, and retained when you combine files. See + :ref:`contexts` for more information. + +- Dynamic contexts: specifying ``[run] dynamic_context = test_function`` in the + config file will record the test function name as a dynamic context during + execution. This is the core of "Who Tests What" (`issue 170`_). Things to + note: + + - There is no reporting support yet. Use SQLite to query the .coverage file + for information. Ideas are welcome about how reporting could be extended + to use this data. + + - There's a noticeable slow-down before any test is run. + + - Data files will now be roughly N times larger, where N is the number of + tests you have. Combining data files is therefore also N times slower. + + - No other values for ``dynamic_context`` are recognized yet. Let me know + what else would be useful. I'd like to use a pytest plugin to get better + information directly from pytest, for example. + +.. _issue 170: https://github.com/nedbat/coveragepy/issues/170 + +- Environment variable substitution in configuration files now supports two + syntaxes for controlling the behavior of undefined variables: if ``VARNAME`` + is not defined, ``${VARNAME?}`` will raise an error, and ``${VARNAME-default + value}`` will use "default value". + + +.. _changes_50a2: + +Version 5.0a2 --- 2018-09-03 +---------------------------- + +- Coverage's data storage has changed. In version 4.x, .coverage files were + basically JSON. Now, they are SQLite databases. This means the data file + can be created earlier than it used to. A large amount of code was + refactored to support this change. + + - Because the data file is created differently than previous releases, you + may need ``parallel=true`` where you didn't before. + + - The old data format is still available (for now) by setting the environment + variable COVERAGE_STORAGE=json. Please tell me if you think you need to + keep the JSON format. + + - The database schema is guaranteed to change in the future, to support new + features. I'm looking for opinions about making the schema part of the + public API to coverage.py or not. + +- Development moved from `Bitbucket`_ to `GitHub`_. + +- HTML files no longer have trailing and extra whitespace. + +- The sort order in the HTML report is stored in local storage rather than + cookies. Thanks, Federico Bond. + +- pickle2json, for converting v3 data files to v4 data files, has been removed. + +.. _Bitbucket: https://bitbucket.org/ned/coveragepy +.. _GitHub: https://github.com/nedbat/coveragepy + + +.. _changes_50a1: + +Version 5.0a1 --- 2018-06-05 +---------------------------- + +- Coverage.py no longer supports Python 2.6 or 3.3. + +- The location of the configuration file can now be specified with a + ``COVERAGE_RCFILE`` environment variable, as requested in `issue 650`_. + +- A new warning (``already-imported``) is issued if measurable files have + already been imported before coverage.py started measurement. See + :ref:`cmd_warnings` for more information. + +- Running coverage many times for small runs in a single process should be + faster, closing `issue 625`_. Thanks, David MacIver. + +- Large HTML report pages load faster. Thanks, Pankaj Pandey. + +.. _issue 625: https://bitbucket.org/ned/coveragepy/issues/625/lstat-dominates-in-the-case-of-small +.. _issue 650: https://bitbucket.org/ned/coveragepy/issues/650/allow-setting-configuration-file-location + .. _changes_451: @@ -485,7 +573,7 @@ Work from the PyCon 2016 Sprints! .. _issue 478: https://bitbucket.org/ned/coveragepy/issues/478/help-shows-silly-program-name-when-running .. _issue 484: https://bitbucket.org/ned/coveragepy/issues/484/multiprocessing-greenlet-concurrency .. _issue 492: https://bitbucket.org/ned/coveragepy/issues/492/subprocess-coverage-strange-detection-of -.. _unittest-mixins: https://pypi.python.org/pypi/unittest-mixins +.. _unittest-mixins: https://pypi.org/project/unittest-mixins/ .. _changes_41: @@ -572,9 +660,9 @@ Version 4.1b2 --- 2016-01-23 - Class docstrings were considered executable. Now they no longer are. - ``yield from`` and ``await`` were considered returns from functions, since - they could tranfer control to the caller. This produced unhelpful "missing - branch" reports in a number of circumstances. Now they no longer are - considered returns. + they could transfer control to the caller. This produced unhelpful + "missing branch" reports in a number of circumstances. Now they no longer + are considered returns. - In unusual situations, a missing branch to a negative number was reported. This has been fixed, closing `issue 466`_. @@ -1804,7 +1892,7 @@ Version 3.2b4 --- 2009-12-01 - On Python 3.x, setuptools has been replaced by `Distribute`_. -.. _Distribute: https://pypi.python.org/pypi/distribute +.. _Distribute: https://pypi.org/project/distribute/ Version 3.2b3 --- 2009-11-23 diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 52cedc0f..011e234c 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -47,11 +47,13 @@ Dmitry Trofimov Eduardo Schettino Emil Madsen Edward Loper +Federico Bond Geoff Bache George Paci George Song Greg Rogers Guillaume Chazarain +Hugo van Kemenade Ilia Meerovich Imri Goldberg Ionel Cristian Mărieș @@ -80,6 +82,8 @@ Mickie Betz Nathan Land Noel O'Boyle Olivier Grisel +Ori Avtalion +Pankaj Pandey Pablo Carballo Patrick Mezard Peter Baughman @@ -98,6 +102,7 @@ Stephen Finucane Steve Leonard Steve Peak Ted Wexler +Thijs Triemstra Titus Brown Ville Skyttä Yury Selivanov diff --git a/MANIFEST.in b/MANIFEST.in index 462f24ff..c9b345e1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # MANIFEST.in file for coverage.py @@ -10,11 +10,9 @@ include MANIFEST.in include Makefile include NOTICE.txt include README.rst -include TODO.txt include __main__.py include .travis.yml include appveyor.yml -include circle.yml include howto.txt include igor.py include metacov.ini @@ -22,6 +20,7 @@ include pylintrc include setup.py include tox.ini include tox_wheels.ini +include .editorconfig recursive-include ci *.* exclude ci/appveyor.token @@ -40,7 +39,5 @@ recursive-include tests/farm */gold*/*.* */gold*/*/*.* recursive-include tests/farm/*/src * *.* recursive-include tests js/*.* qunit/*.* prune tests/eggsrc/build -prune tests/eggsrc/dist -prune tests/eggsrc/*.egg-info global-exclude *.py[co] @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Makefile for utility work on coverage.py. @@ -21,14 +21,19 @@ clean: -rm -f coverage/*,cover -rm -f MANIFEST -rm -f .coverage .coverage.* coverage.xml .metacov* + -rm -f */.coverage */*/.coverage */*/*/.coverage */*/*/*/.coverage */*/*/*/*/.coverage */*/*/*/*/*/.coverage -rm -f tests/zipmods.zip -rm -rf tests/eggsrc/build tests/eggsrc/dist tests/eggsrc/*.egg-info -rm -f setuptools-*.egg distribute-*.egg distribute-*.tar.gz -rm -rf doc/_build doc/_spell doc/sample_html_beta -rm -rf .tox_kits + -rm -rf .cache .pytest_cache + -rm -rf $$TMPDIR/coverage_test sterile: clean -rm -rf .tox* + -docker image rm -f quay.io/pypa/manylinux1_i686 quay.io/pypa/manylinux1_x86_64 + LINTABLE = coverage tests igor.py setup.py __main__.py @@ -38,22 +43,19 @@ lint: todo: -grep -R --include=*.py TODO $(LINTABLE) -spell: - -pylint --disable=all --enable=spelling $(LINTABLE) - pep8: pycodestyle --filename=*.py --repeat $(LINTABLE) test: tox -e py27,py35 $(ARGS) -TOX_SMOKE_ARGS = -n 6 -m "not expensive" --maxfail=3 $(ARGS) +PYTEST_SMOKE_ARGS = -n 6 -m "not expensive" --maxfail=3 $(ARGS) smoke: - COVERAGE_NO_PYTRACER=1 tox -e py26,py33 -- $(TOX_SMOKE_ARGS) + COVERAGE_NO_PYTRACER=1 tox -q -e py27,py34 -- $(PYTEST_SMOKE_ARGS) pysmoke: - COVERAGE_NO_CTRACER=1 tox -e py26,py33 -- $(TOX_SMOKE_ARGS) + COVERAGE_NO_CTRACER=1 tox -q -e py27,py34 -- $(PYTEST_SMOKE_ARGS) metacov: COVERAGE_COVERAGE=yes tox $(ARGS) @@ -69,9 +71,6 @@ kit: wheel: tox -c tox_wheels.ini $(ARGS) -manylinux_clean: - docker image rm quay.io/pypa/manylinux1_i686 quay.io/pypa/manylinux1_x86_64 - manylinux: docker run -it --init --rm -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/ci/manylinux.sh build docker run -it --init --rm -v `pwd`:/io quay.io/pypa/manylinux1_i686 /io/ci/manylinux.sh build @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt =========== Coverage.py @@ -7,32 +7,39 @@ Coverage.py Code coverage testing for Python. -| |license| |versions| |status| |docs| -| |ci-status| |win-ci-status| |codecov| -| |kit| |format| |saythanks| - -.. downloads badge seems to be broken... |downloads| +| |license| |versions| |status| +| |ci-status| |win-ci-status| |docs| |codecov| +| |kit| |format| |repos| +| |tidelift| |saythanks| 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. +Professional support for coverage.py is available as part of the `Tidelift +Subscription`_. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme + Coverage.py runs on many versions of Python: -* CPython 2.6, 2.7 and 3.3 through 3.7. -* PyPy2 5.10 and PyPy3 5.10. +* CPython 2.7. +* CPython 3.4 through 3.7. +* PyPy2 6.0 and PyPy3 6.0. * Jython 2.7.1, though not for reporting. * IronPython 2.7.7, though not for reporting. Documentation is on `Read the Docs`_. Code repository and issue tracker are on -`Bitbucket`_, with a mirrored repository on `GitHub`_. +`GitHub`_. .. _Read the Docs: https://coverage.readthedocs.io/ -.. _Bitbucket: https://bitbucket.org/ned/coveragepy .. _GitHub: https://github.com/nedbat/coveragepy -**New in 4.5:** Configurator plug-ins. +**New in 5.0:** SQLite data storage, contexts, dropped support for Python 2.6 +and 3.3. + +New in 4.5: Configurator plug-ins. New in 4.4: Suppressable warnings, continuous coverage measurement. @@ -69,11 +76,11 @@ License Licensed under the `Apache 2.0 License`_. For details, see `NOTICE.txt`_. .. _Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 -.. _NOTICE.txt: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. _NOTICE.txt: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt -.. |ci-status| image:: https://travis-ci.org/nedbat/coveragepy.svg?branch=master - :target: https://travis-ci.org/nedbat/coveragepy +.. |ci-status| image:: https://travis-ci.com/nedbat/coveragepy.svg?branch=master + :target: https://travis-ci.com/nedbat/coveragepy :alt: Build status .. |win-ci-status| image:: https://ci.appveyor.com/api/projects/status/kmeqpdje7h9r6vsf/branch/master?svg=true :target: https://ci.appveyor.com/project/nedbat/coveragepy @@ -85,26 +92,32 @@ Licensed under the `Apache 2.0 License`_. For details, see `NOTICE.txt`_. :target: https://requires.io/github/nedbat/coveragepy/requirements/?branch=master :alt: Requirements status .. |kit| image:: https://badge.fury.io/py/coverage.svg - :target: https://pypi.python.org/pypi/coverage + :target: https://pypi.org/project/coverage/ :alt: PyPI status .. |format| image:: https://img.shields.io/pypi/format/coverage.svg - :target: https://pypi.python.org/pypi/coverage + :target: https://pypi.org/project/coverage/ :alt: Kit format .. |downloads| image:: https://img.shields.io/pypi/dw/coverage.svg - :target: https://pypi.python.org/pypi/coverage + :target: https://pypi.org/project/coverage/ :alt: Weekly PyPI downloads .. |versions| image:: https://img.shields.io/pypi/pyversions/coverage.svg - :target: https://pypi.python.org/pypi/coverage + :target: https://pypi.org/project/coverage/ :alt: Python versions supported .. |status| image:: https://img.shields.io/pypi/status/coverage.svg - :target: https://pypi.python.org/pypi/coverage + :target: https://pypi.org/project/coverage/ :alt: Package stability .. |license| image:: https://img.shields.io/pypi/l/coverage.svg - :target: https://pypi.python.org/pypi/coverage + :target: https://pypi.org/project/coverage/ :alt: License -.. |codecov| image:: http://codecov.io/github/nedbat/coveragepy/coverage.svg?branch=master&precision=2 - :target: http://codecov.io/github/nedbat/coveragepy?branch=master +.. |codecov| image:: https://codecov.io/github/nedbat/coveragepy/coverage.svg?branch=master&precision=2 + :target: https://codecov.io/github/nedbat/coveragepy?branch=master :alt: Coverage! +.. |repos| image:: https://repology.org/badge/tiny-repos/python:coverage.svg + :target: https://repology.org/metapackage/python:coverage/versions + :alt: Packaging status .. |saythanks| image:: https://img.shields.io/badge/saythanks.io-%E2%98%BC-1EAEDB.svg :target: https://saythanks.io/to/nedbat :alt: Say thanks :) +.. |tidelift| image:: https://tidelift.com/badges/github/nedbat/coveragepy + :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme + :alt: Tidelift diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index f6036d2e..00000000 --- a/TODO.txt +++ /dev/null @@ -1,285 +0,0 @@ -Coverage.py TODO - -Key: - * Heading - - Not done yet. - + Done. - x Not going to do. - -* 4.0 - -- What defaults should change? - x --source = . ? - x --branch = True ? - -- Remove 2.3, 2.4, 2.5 limitations - + set, sorted, reversed, rpartition - + generator expressions - + decorators - + collections.defaultdict - + .startswith((,)) - + "with" statements - - .format() ? - + try/except/finally - + with assertRaises - + addCleanup instead of tearDown - + exec statement can look like a function in py2 (since when?) - - runpy ? - + we can use "except ExcClass as e:" - -- Plugins - + Clean up - + implement plugin support in CTracer - + remove plugin support from PyTracer - x add services: - - filelocator - - warning - - dynamic_source_filename: return should be a canonical path - - update the omit test to use "quux*" instead of "*quux*" - + docs -+ Make reports use filenames, not module names -- documentation - - test helpers - + cov.config["run:branch"] api (well, coverage.get_option etc) - + "added in 4.0" - - tweaks to theme? - - Plugins! - Once per process - Once per file - - create a file tracer - - call its has_dynamic_source_file() - Once per call - Once per line -- build process - - don't publish to nedbat.com any more (but still need the sample html reports) - + don't need .px tooling - - write a new nedbat.com/code/coverage page. - - all doc links should point to rtfd -+ Remove code only run on <2.6 -+ Change data file to json -+ Create data api -+ gevent, etc. -+ Remove the old command-line syntax - + A pain, b/c of the structure of the tests. - + BTW: make an easier way to write those tests. - -- tests - - test the kit has the right contents - - test the kit installs the right stuff - - -* --source stuff: - + warn if a package is never found. - + warn if no data was collected - - tie --source into reporting - -* Soon - -+ Better omit handling that ignores files during measurement. - - Deal with ~ in specified paths correctly. -+ while TRUE claims to be partial. - + A way to mark lines as partial branches, with a regex? - + Default to "while True:", "while 1:" -+ HTML keyboard short cuts - - -* 3.2 - -+ Some kind of indication in the HTML where yellow lines aren't going. -- Profile the reporting code: it's REALLY slow. - - parser is doing some redundant work. -+ Analysis class should do rolling up of stats also (actually Numbers) -+ Update docs for --branch. -x self.coverage.data.has_arcs is ugly. -+ Branches that never jump to nocover lines shouldn't be marked as partial. - (see top of test_cogapp for examples) -+ Maybe turning off yellow lines should make those lines green? -+ A missing branch to leave the function shows an annotation of -1. Now "exit". -+ XML report needs to get branch information. -+ Add branch info to "coverage debug data" -+ Polish up the help, and double-check the docs. - - -* Speed - -+ C extension collector -- bitvector in trace extension. -- Ignore certain modules -+ Record linenos rather than (file,lineno) pairs in tracer. -x Tricky swapping of collector like figleaf, pycov, et al. (Don't need to do - this with C collector). -- Seems like there should be a faster way to manage all the line number sets in - CodeParser.raw_parse. -- If tracing, canonical_filename_cache overlaps with should_trace_cache. Skip - canonical_filename_cache. Maybe it isn't even worth it... -- Would pre-allocating line number integers make the C tracer faster? It would - use less memory. - - -* Accuracy - -- Record magic number of module to ensure code hasn't changed -- Record version of coverage data file, so we can update what's stored there. -- Record options in coverage data file, so multiple runs are certain to make - sense together. -- Do I still need the lines in annotate_file that deal specially with "else"? - - -* Power - -+ Branch coverage - Titus' idea: - 1: if a: - 2: b = 2 - 3: c = 3 - if the coverage data shows 1,2,3, it was if-then. if it's 1,3, then the - missing else was executed. -+ API for getting coverage data. -- Instruction tracing instead of line tracing. -- Path tracing (how does this even work?) -- Count execution of lines -- Track callers of functions (ala std module trace) -- Method/Class/Module coverage reporting. -- .coverage files that can be kept separate, rather than accumulated. -- test/coverage map: http://rbtcollins.wordpress.com/2009/09/16/back-from-hiatus/ - - Similar to figleaf's sections. - - -* Convenience - -- Command line modules should also be directories, meaning all the modules in that - directory. -- Why can't a morf also be a string, the name of a module? -- ignore by module as well as file? -+ Use a .coveragerc file to control coverage.py without the programmatic API. -- Add a --data switch to explicitly control the data file on the command line. -x Why can't you specify execute (-x) and report (-r) in the same invocation? - Maybe just because -x needs the rest of the command line? -+ Support 2.3 - 3.1! - http://pythonology.blogspot.com/2009/02/making-code-run-on-python-20-through-30.html - http://www.rfk.id.au/blog/entry/preparing-pyenchant-for-python-3 - http://pydev.blogspot.com/2008/11/making-code-work-in-python-2-and-3.html - + Explicitly set pickle protocol to 2. -- An inference mode that marks lines as executed if they "must have been" executed: - class definitions, etc, when coverage is started after the class is defined. -- Different categories of exclude pragma? So you can enable and disable them - from the command line, to reconsider exclusions. -+ Reporting on files never touched by coverage.py (package completeness) -- A setup.py command? http://jeetworks.org/node/50 -- Deltas: indicate the change in coverage percentage from the last run. -+ Show lines missing rather than lines run in the reporting, since that's what - you need to focus on. - - -* Beauty - -+ HTML report - - Colored bars indicating coverage per file. - - Package navigation. - - Rolled-up statistics. - - Some way to focus in on red and yellow - - Show only lines near highlights? - + Jump to next highlight? - + Keyboard navigation: j and k. - - Cookie for changes to pyfile.html state. - + Clickable column headers on the index page. - + Syntax coloring in HTML report. - + Dynamic effects in HTML report. - + Footer in reports pointing to coverage.py home page. - + Baseline grid for linenumber font. - + Separate out css and HTML. - + Does it work right with utf-8 source files? http://www.python.org/dev/peps/pep-0263/ - - Use vim modeline to determine tab width: http://vimdoc.sourceforge.net/htmldoc/options.html#modeline - - -* Community - -+ New docs, rather than pointing to Gareth's - + Min python version is 2.3. - - Three phases of work: - - Collection - - Analysis - - Reporting - - Distinction between: - - ignore (files not to collect) - - exclude (lines not to report as missed) - - omit (files not to report) - - Changes from coverage.py 2.x: - - Bare "except:" lines now count as executable code. - - Double function decorators: all decorator lines count as executable code. - x Document the .coverage file format. - + HTML reporting. - - Much more detail about what's in the report. - - References between pages are off: - - They have <em> tags around them. - - They use #anchors that don't survive the px->html conversion. -+ Be sure --help text is complete (-i is missing). -+ Host the project somewhere with a real bug tracker: bitbucket.org -+ Point discussion to TIP -- PEP 8 compliance? - - -* Programmability - -+ Don't use sys.exit in CoverageScript. -+ Remove singleton - + Initialization of instance variables in the class. - - -* Installation - -x How will coverage.py package install over coverage.py module? -x pip can't install it: it reads the coverage.py html page, and finds the kit link, - but then can't handle the root-relative link. - - -* Modernization - -+ Decide on minimum supported version - + 2.3 - + Get rid of the basestring protection - + Use enumerate - + Use sets instead of dicts -+ Switch from getopt to optparse. -+ Get rid of the recursive nonsense. -+ Docstrings. -+ Remove huge document-style comments. -- Better names: - + self.cache -> self.cache_filename -> CoverageData.filename - + self.usecache -> CoverageData.use_file -- More classes: - - Module munging - + Coverage data files -+ Why are some imports at the top of the file, and some in functions? -+ Get rid of sys.exitfunc use. -+ True and False (with no backward adaptation: the constants are new in 2.2.1) -+ Get rid of compiler module - + In analyzing code - + In test_coverage.py -+ Style: - + lineno - + filename - - -* Correctness - -- What does -p (parallel mode) mean with -e (erase data)? - - -* Tests - -+ Switch to a real test runner, like nose. -+ Test both the C trace function and the Python trace function. -+ parser.py has no direct tests. -+ Tests about the .coverage file. -+ Tests about the --long-form of arguments. -+ Tests about overriding the .coverage filename. -- Tests about parallel mode. -+ Tests about assigning a multi-line string. -- Tests about tricky docstrings. -+ Coverage test coverage.py! -- Tests that tracing stops after calling stop() -- More intensive thread testing. -x Tests about the "import __main__" in cmdline.py -+ What happens if the -x script raises an exception? -- Test that the kit has all the proper contents. diff --git a/__main__.py b/__main__.py index c998e1da..28ad7d2d 100644 --- a/__main__.py +++ b/__main__.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Be able to execute coverage.py by pointing Python at a working tree.""" @@ -8,12 +8,5 @@ import os PKG = 'coverage' -try: - run_globals = runpy.run_module(PKG, run_name='__main__', alter_sys=True) - executed = os.path.splitext(os.path.basename(run_globals['__file__']))[0] - if executed != '__main__': # For Python 2.5 compatibility - raise ImportError( - 'Incorrectly executed %s instead of __main__' % executed - ) -except ImportError: # For Python 2.6 compatibility - runpy.run_module('%s.__main__' % PKG, run_name='__main__', alter_sys=True) +run_globals = runpy.run_module(PKG, run_name='__main__', alter_sys=True) +executed = os.path.splitext(os.path.basename(run_globals['__file__']))[0] diff --git a/appveyor.yml b/appveyor.yml index f6b40660..8b9e1b61 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,6 +5,9 @@ version: '{branch}-{build}' shallow_clone: true +cache: + - '%LOCALAPPDATA%\pip\Cache' + environment: CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci\\run_with_env.cmd" @@ -96,16 +99,16 @@ install: # Upgrade to the latest version of pip to avoid it displaying warnings # about it being out of date. - - "pip install --disable-pip-version-check --user --upgrade pip" + - "python -m pip install --disable-pip-version-check --upgrade pip" # And upgrade virtualenv to get the latest pip inside .tox virtualenvs. - - "pip install --disable-pip-version-check --user --upgrade virtualenv" + - "python -m pip install --disable-pip-version-check --upgrade virtualenv" # Install requirements. - "%CMD_IN_ENV% pip install -r requirements/ci.pip" # Make a python3.4.bat file in the current directory so that tox will find it # and python3.4 will mean what we want it to. - - "python -c \"import os; open('python{0}.{1}.bat'.format(*os.environ['TOXENV'][2:]), 'w').write('@{0}\\\\python \\x25*\\n'.format(os.environ['PYTHON']))\"" + - "python -c \"import os; open('python{}.{}.bat'.format(*os.environ['TOXENV'][2:]), 'w').write('@{}\\\\python \\x25*\\n'.format(os.environ['PYTHON']))\"" build_script: # If not a metacov job, then build wheels and .exe installers. diff --git a/ci/download_appveyor.py b/ci/download_appveyor.py index daf6f06e..7cec413c 100644 --- a/ci/download_appveyor.py +++ b/ci/download_appveyor.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Use the Appveyor API to download Windows artifacts.""" diff --git a/circle.yml b/circle.yml deleted file mode 100644 index a52959ef..00000000 --- a/circle.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Circle CI configuration for coverage.py. -# https://circleci.com/gh/nedbat/coveragepy - -machine: - python: - version: 2.7.6 - post: - - pyenv global pypy-2.4.0 2.6.8 2.7.9 3.3.3 3.4.2 - -dependencies: - pre: - - pip install -U pip - override: - - pip install -r requirements/tox.pip - -test: - override: - - tox diff --git a/coverage/__init__.py b/coverage/__init__.py index 63f488f2..331b304b 100644 --- a/coverage/__init__.py +++ b/coverage/__init__.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Code coverage measurement for Python. @@ -8,11 +8,12 @@ https://nedbatchelder.com/code/coverage """ +import sys + from coverage.version import __version__, __url__, version_info from coverage.control import Coverage, process_startup from coverage.data import CoverageData -from coverage.debug import enable_aspectlib_maybe from coverage.misc import CoverageException from coverage.plugin import CoveragePlugin, FileTracer, FileReporter from coverage.pytracer import PyTracer @@ -20,19 +21,15 @@ from coverage.pytracer import PyTracer # Backward compatibility. coverage = Coverage -# Possibly enable aspectlib to debug our execution. -enable_aspectlib_maybe() - # 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 +import encodings.utf_8 # pylint: disable=wrong-import-position, wrong-import-order # 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: diff --git a/coverage/__main__.py b/coverage/__main__.py index 35ab87a5..79aa4e2b 100644 --- a/coverage/__main__.py +++ b/coverage/__main__.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Coverage.py's main entry point.""" diff --git a/coverage/annotate.py b/coverage/annotate.py index 4060450f..48e2b91c 100644 --- a/coverage/annotate.py +++ b/coverage/annotate.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Source file annotation for coverage.py.""" diff --git a/coverage/backunittest.py b/coverage/backunittest.py index 09574ccb..21d7bcb2 100644 --- a/coverage/backunittest.py +++ b/coverage/backunittest.py @@ -1,14 +1,9 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Implementations of unittest features from the future.""" -# Use unittest2 if it's available, otherwise unittest. This gives us -# back-ported features for 2.6. -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest def unittest_has(method): diff --git a/coverage/backward.py b/coverage/backward.py index 5aff6406..b43e35f3 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Add things to old Pythons so I can pretend they are newer.""" @@ -38,12 +38,6 @@ try: except NameError: unicode_class = str -# Where do pickles come from? -try: - import cPickle as pickle -except ImportError: - import pickle - # range or xrange? try: range = xrange # pylint: disable=redefined-builtin diff --git a/coverage/bytecode.py b/coverage/bytecode.py index d823c67c..943f29e1 100644 --- a/coverage/bytecode.py +++ b/coverage/bytecode.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Bytecode manipulation for coverage.py""" diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 7b86054e..edbc1d25 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Command-line support for coverage.py.""" @@ -14,6 +14,7 @@ import traceback from coverage import env from coverage.collector import CTracer +from coverage.data import line_counts from coverage.debug import info_formatter, info_header from coverage.execfile import run_python_file, run_python_module from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource @@ -42,9 +43,13 @@ class Opts(object): "Valid values are: %s." ) % ", ".join(CONCURRENCY_CHOICES), ) + context = optparse.make_option( + '', '--context', action='store', metavar="LABEL", + help="The context label to record for this coverage run", + ) debug = optparse.make_option( '', '--debug', action='store', metavar="OPTS", - help="Debug options, separated by commas", + help="Debug options, separated by commas. [env: COVERAGE_DEBUG]", ) directory = optparse.make_option( '-d', '--directory', action='store', metavar="DIR", @@ -115,7 +120,11 @@ class Opts(object): ) rcfile = optparse.make_option( '', '--rcfile', action='store', - help="Specify configuration file. Defaults to '.coveragerc'", + help=( + "Specify configuration file. " + "By default '.coveragerc', 'setup.cfg' and 'tox.ini' are tried. " + "[env: COVERAGE_RCFILE]" + ), ) source = optparse.make_option( '', '--source', action='store', metavar="SRC1,SRC2,...", @@ -124,7 +133,7 @@ class Opts(object): timid = optparse.make_option( '', '--timid', action='store_true', help=( - "Use a simpler but slower trace method. Try this if you get " + "Use a simpler but slower trace method. Try this if you get " "seemingly impossible results!" ), ) @@ -155,6 +164,7 @@ class CoverageOptionParser(optparse.OptionParser, object): append=None, branch=None, concurrency=None, + context=None, debug=None, directory=None, fail_under=None, @@ -353,6 +363,7 @@ CMDS = { Opts.append, Opts.branch, Opts.concurrency, + Opts.context, Opts.include, Opts.module, Opts.omit, @@ -386,8 +397,10 @@ OK, ERR, FAIL_UNDER = 0, 1, 2 class CoverageScript(object): """The command-line interface to coverage.py.""" - def __init__(self, _covpkg=None, _run_python_file=None, - _run_python_module=None, _help_fn=None, _path_exists=None): + 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 @@ -399,7 +412,6 @@ class CoverageScript(object): 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.path_exists = _path_exists or os.path.exists self.global_option = False self.coverage = None @@ -475,6 +487,8 @@ class CoverageScript(object): include=include, debug=debug, concurrency=options.concurrency, + check_preimported=True, + context=options.context, ) if options.action == "debug": @@ -607,14 +621,14 @@ class CoverageScript(object): # they will be None if they have not been specified. if getattr(options, opt_name) is not None: self.help_fn( - "Options affecting multiprocessing must be specified " - "in a configuration file." + "Options affecting multiprocessing must only be specified " + "in a configuration file.\n" + "Remove --{} from the command line.".format(opt_name) ) return ERR - if not self.coverage.get_option("run:parallel"): - if not options.append: - self.coverage.erase() + if options.append: + self.coverage.load() # Run the script. self.coverage.start() @@ -623,18 +637,13 @@ class CoverageScript(object): if options.module: self.run_python_module(args[0], args) else: - filename = args[0] - self.run_python_file(filename, args) + self.run_python_file(args[0], args) except NoSource: code_ran = False raise finally: self.coverage.stop() if code_ran: - if options.append: - data_file = self.coverage.get_option("run:data_file") - if self.path_exists(data_file): - self.coverage.combine(data_paths=[data_file]) self.coverage.save() return OK @@ -654,12 +663,12 @@ class CoverageScript(object): print(" %s" % line) elif info == 'data': self.coverage.load() - data = self.coverage.data + data = self.coverage.get_data() print(info_header("data")) - print("path: %s" % self.coverage.data_files.filename) + print("path: %s" % self.coverage.get_data().filename) if data: print("has_arcs: %r" % data.has_arcs()) - summary = data.line_counts(fullpath=True) + summary = line_counts(data, fullpath=True) filenames = sorted(summary.keys()) print("\n%d files:" % len(filenames)) for f in filenames: diff --git a/coverage/collector.py b/coverage/collector.py index 72ab32b6..4e7058a0 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Raw data collector for coverage.py.""" @@ -9,6 +9,7 @@ import sys from coverage import env from coverage.backward import litems, range # pylint: disable=redefined-builtin from coverage.debug import short_stack +from coverage.disposition import FileDisposition from coverage.files import abs_file from coverage.misc import CoverageException, isolate_module from coverage.pytracer import PyTracer @@ -33,19 +34,6 @@ except ImportError: CTracer = None -class FileDisposition(object): - """A simple value type for recording what to do with a file.""" - pass - - -def should_start_context(frame): - """Who-Tests-What hack: Determine whether this frame begins a new who-context.""" - fn_name = frame.f_code.co_name - if fn_name.startswith("test"): - return fn_name - return None - - class Collector(object): """Collects trace data. @@ -70,7 +58,10 @@ class Collector(object): # The concurrency settings we support here. SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"]) - def __init__(self, should_trace, check_include, timid, branch, warn, concurrency): + def __init__( + self, should_trace, check_include, should_start_context, + timid, branch, warn, concurrency, + ): """Create a collector. `should_trace` is a function, taking a file name and a frame, and @@ -79,6 +70,11 @@ class Collector(object): `check_include` is a function taking a file name and a frame. It returns a boolean: True if the file should be traced, False if not. + `should_start_context` is a function taking a frame, and returning a + string. If the frame should be the start of a new context, the string + is the new context. If the frame should not be the start of a new + context, return None. + 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 @@ -100,13 +96,18 @@ class Collector(object): """ self.should_trace = should_trace self.check_include = check_include + self.should_start_context = should_start_context self.warn = warn self.branch = branch self.threading = None + self.covdata = None + + self.static_context = None self.origin = short_stack() self.concur_id_func = None + self.abs_file_cache = {} # We can handle a few concurrency options here, but only one at a time. these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency) @@ -139,10 +140,6 @@ class Collector(object): ) ) - # Who-Tests-What is just a hack at the moment, so turn it on with an - # environment variable. - self.wtw = int(os.getenv('COVERAGE_WTW', 0)) - self.reset() if timid: @@ -163,13 +160,23 @@ class Collector(object): def __repr__(self): return "<Collector at 0x%x: %s>" % (id(self), self.tracer_name()) + def use_data(self, covdata, context): + """Use `covdata` for recording data.""" + self.covdata = covdata + self.static_context = context + self.covdata.set_context(self.static_context) + def tracer_name(self): """Return the class name of the tracer we're using.""" return self._trace_class.__name__ def _clear_data(self): """Clear out existing data, but stay ready for more collection.""" - self.data.clear() + # We used to used self.data.clear(), but that would remove filename + # keys and data values that were still in use higher up the stack + # when we are called as part of switch_context. + for d in self.data.values(): + d.clear() for tracer in self.tracers: tracer.reset_activity() @@ -181,10 +188,6 @@ class Collector(object): # pairs as keys (if branch coverage). self.data = {} - # A dict mapping contexts to data dictionaries. - self.contexts = {} - self.contexts[None] = self.data - # A dictionary mapping file names to file tracer plugin names that will # handle them. self.file_tracers = {} @@ -246,11 +249,13 @@ class Collector(object): tracer.threading = self.threading if hasattr(tracer, 'check_include'): tracer.check_include = self.check_include - if self.wtw: - if hasattr(tracer, 'should_start_context'): - tracer.should_start_context = should_start_context - if hasattr(tracer, 'switch_context'): - tracer.switch_context = self.switch_context + if hasattr(tracer, 'should_start_context'): + tracer.should_start_context = self.should_start_context + tracer.switch_context = self.switch_context + elif self.should_start_context: + raise CoverageException( + "Can't support dynamic contexts with {}".format(self.tracer_name()) + ) fn = tracer.start() self.tracers.append(tracer) @@ -366,15 +371,29 @@ class Collector(object): return any(tracer.activity() for tracer in self.tracers) def switch_context(self, new_context): - """Who-Tests-What hack: switch to a new who-context.""" - # Make a new data dict, or find the existing one, and switch all the - # tracers to use it. - data = self.contexts.setdefault(new_context, {}) - for tracer in self.tracers: - tracer.data = data + """Switch to a new dynamic context.""" + self.flush_data() + if self.static_context: + context = self.static_context + if new_context: + context += ":" + new_context + else: + context = new_context + self.covdata.set_context(context) + + def cached_abs_file(self, filename): + """A locally cached version of `abs_file`.""" + key = (type(filename), filename) + try: + return self.abs_file_cache[key] + except KeyError: + return self.abs_file_cache.setdefault(key, abs_file(filename)) + + def flush_data(self): + """Save the collected data to our associated `CoverageData`. - def save_data(self, covdata): - """Save the collected data to a `CoverageData`. + Data may have also been saved along the way. This forces the + last of the data to be saved. Returns True if there was data to save, False if not. """ @@ -398,20 +417,13 @@ class Collector(object): else: raise runtime_err # pylint: disable=raising-bad-type - return dict((abs_file(k), v) for k, v in items) + return dict((self.cached_abs_file(k), v) for k, v in items if v) if self.branch: - covdata.add_arcs(abs_file_dict(self.data)) + self.covdata.add_arcs(abs_file_dict(self.data)) else: - covdata.add_lines(abs_file_dict(self.data)) - covdata.add_file_tracers(abs_file_dict(self.file_tracers)) - - if self.wtw: - # Just a hack, so just hack it. - import pprint - out_file = "coverage_wtw_{:06}.py".format(os.getpid()) - with open(out_file, "w") as wtw_out: - pprint.pprint(self.contexts, wtw_out) + self.covdata.add_lines(abs_file_dict(self.data)) + self.covdata.add_file_tracers(abs_file_dict(self.file_tracers)) self._clear_data() return True diff --git a/coverage/config.py b/coverage/config.py index 7b8f2bd0..2a281875 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -1,15 +1,16 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Config file for coverage.py""" import collections import os import re -import sys +from coverage import env from coverage.backward import configparser, iitems, string_class from coverage.misc import contract, CoverageException, isolate_module +from coverage.misc import substitute_variables os = isolate_module(os) @@ -33,7 +34,7 @@ class HandyConfigParser(configparser.RawConfigParser): def read(self, filenames): """Read a file name as UTF-8 configuration data.""" kwargs = {} - if sys.version_info >= (3, 2): + if env.PYVERSION >= (3, 2): kwargs['encoding'] = "utf-8" return configparser.RawConfigParser.read(self, filenames, **kwargs) @@ -85,23 +86,7 @@ class HandyConfigParser(configparser.RawConfigParser): raise configparser.NoOptionError v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs) - def dollar_replace(m): - """Called for each $replacement.""" - # Only one of the groups will have matched, just get its text. - word = next(w for w in m.groups() if w is not None) # pragma: part covered - 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) + v = substitute_variables(v) return v def getlist(self, section, option): @@ -175,8 +160,13 @@ class CoverageConfig(object): def __init__(self): """Initialize the configuration attributes to their defaults.""" # Metadata about the config. + # We tried to read these config files. self.attempted_config_files = [] - self.config_files = [] + # We did read these config files, but maybe didn't find any content for us. + self.config_files_read = [] + # The file that gave us our configuration. + self.config_file = None + self._config_contents = None # Defaults for [run] and [report] self._include = None @@ -185,10 +175,12 @@ class CoverageConfig(object): # Defaults for [run] self.branch = False self.concurrency = None + self.context = None self.cover_pylib = False self.data_file = ".coverage" self.debug = [] self.disable_warnings = [] + self.dynamic_context = None self.note = None self.parallel = False self.plugins = [] @@ -262,7 +254,7 @@ class CoverageConfig(object): if not files_read: return False - self.config_files.extend(files_read) + self.config_files_read.extend(files_read) any_set = False try: @@ -305,9 +297,16 @@ class CoverageConfig(object): # then it was used. If we're piggybacking on someone else's file, # then it was only used if we found some settings in it. if our_file: - return True + used = True else: - return any_set + used = any_set + + if used: + self.config_file = filename + with open(filename) as f: + self._config_contents = f.read() + + return used CONFIG_FILE_OPTIONS = [ # These are *args for _set_attr_from_config_option: @@ -321,10 +320,12 @@ class CoverageConfig(object): # [run] ('branch', 'run:branch', 'boolean'), ('concurrency', 'run:concurrency', 'list'), + ('context', 'run:context'), ('cover_pylib', 'run:cover_pylib', 'boolean'), ('data_file', 'run:data_file'), ('debug', 'run:debug', 'list'), ('disable_warnings', 'run:disable_warnings', 'list'), + ('dynamic_context', 'run:dynamic_context'), ('note', 'run:note'), ('parallel', 'run:parallel', 'boolean'), ('plugins', 'run:plugins', 'list'), @@ -425,6 +426,34 @@ class CoverageConfig(object): raise CoverageException("No such option: %r" % option_name) +def config_files_to_try(config_file): + """What config files should we try to read? + + Returns a list of tuples: + (filename, is_our_file, was_file_specified) + """ + + # Some API users were specifying ".coveragerc" to mean the same as + # True, so make it so. + if config_file == ".coveragerc": + config_file = True + specified_file = (config_file is not True) + if not specified_file: + # No file was specified. Check COVERAGE_RCFILE. + config_file = os.environ.get('COVERAGE_RCFILE') + if config_file: + specified_file = True + if not specified_file: + # Still no file specified. Default to .coveragerc + config_file = ".coveragerc" + files_to_try = [ + (config_file, True, specified_file), + ("setup.cfg", False, False), + ("tox.ini", False, False), + ] + return files_to_try + + def read_coverage_config(config_file, **kwargs): """Read the coverage.py configuration. @@ -435,10 +464,7 @@ def read_coverage_config(config_file, **kwargs): setting values in the configuration. Returns: - config_file, config: - config_file is the value to use for config_file in other - invocations of coverage. - + config: config is a CoverageConfig object read from the appropriate configuration file. @@ -449,25 +475,14 @@ def read_coverage_config(config_file, **kwargs): # 2) from a file: if config_file: - # Some API users were specifying ".coveragerc" to mean the same as - # True, so make it so. - if config_file == ".coveragerc": - config_file = True - specified_file = (config_file is not True) - if not specified_file: - config_file = ".coveragerc" - - for fname, our_file in [(config_file, True), - ("setup.cfg", False), - ("tox.ini", False)]: - config_read = config.from_file(fname, our_file=our_file) - is_config_file = fname == config_file - - if not config_read and is_config_file and specified_file: - raise CoverageException("Couldn't read '%s' as a config file" % fname) + files_to_try = config_files_to_try(config_file) + for fname, our_file, specified_file in files_to_try: + config_read = config.from_file(fname, our_file=our_file) if config_read: break + if specified_file: + raise CoverageException("Couldn't read '%s' as a config file" % fname) # 3) from environment variables: env_data_file = os.environ.get('COVERAGE_FILE') @@ -486,4 +501,4 @@ def read_coverage_config(config_file, **kwargs): config.html_dir = os.path.expanduser(config.html_dir) config.xml_output = os.path.expanduser(config.xml_output) - return config_file, config + return config diff --git a/coverage/control.py b/coverage/control.py index b82c8047..4f2afda0 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -1,36 +1,30 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Core control stuff for coverage.py.""" - import atexit -import inspect -import itertools import os import platform -import re import sys import time -import traceback from coverage import env from coverage.annotate import AnnotateReporter from coverage.backward import string_class, iitems -from coverage.collector import Collector +from coverage.collector import Collector, CTracer from coverage.config import read_coverage_config -from coverage.data import CoverageData, CoverageDataFiles +from coverage.data import CoverageData, combine_parallel_data from coverage.debug import DebugControl, write_formatted_info -from coverage.files import TreeMatcher, FnmatchMatcher -from coverage.files import PathAliases, find_python_files, prep_patterns -from coverage.files import canonical_filename, set_relative_directory -from coverage.files import ModuleMatcher, abs_file +from coverage.disposition import disposition_debug_msg +from coverage.files import PathAliases, set_relative_directory, abs_file from coverage.html import HtmlReporter +from coverage.inorout import InOrOut from coverage.misc import CoverageException, bool_or_none, join_regex from coverage.misc import file_be_gone, isolate_module from coverage.plugin import FileReporter from coverage.plugin_support import Plugins -from coverage.python import PythonFileReporter, source_for_file +from coverage.python import PythonFileReporter from coverage.results import Analysis, Numbers from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter @@ -43,22 +37,6 @@ except ImportError: # pragma: only jytho os = isolate_module(os) -# Pypy has some unusual stuff in the "stdlib". Consider those locations -# when deciding where the stdlib is. These modules are not used for anything, -# they are modules importable from the pypy lib directories, so that we can -# find those directories. -_structseq = _pypy_irc_topic = None -if env.PYPY: - try: - import _structseq - except ImportError: - pass - - try: - import _pypy_irc_topic - except ImportError: - pass - class Coverage(object): """Programmatic access to coverage.py. @@ -74,11 +52,12 @@ class Coverage(object): 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, debug=None, - concurrency=None, + concurrency=None, check_preimported=False, context=None, ): """ `data_file` is the base name of the data file to use, defaulting to @@ -132,58 +111,64 @@ class Coverage(object): "eventlet", "gevent", "multiprocessing", or "thread" (the default). This can also be a list of these strings. + If `check_preimported` is true, then when coverage is started, the + aleady-imported files will be checked to see if they should be measured + by coverage. Importing measured files before coverage is started can + mean that code is missed. + + `context` is a string to use as the context label for collected data. + .. versionadded:: 4.0 The `concurrency` parameter. .. versionadded:: 4.2 The `concurrency` parameter can now be a list of strings. + .. versionadded:: 5.0 + The `check_preimported` and `context` parameters. + """ # Build our configuration from a number of sources. - self.config_file, self.config = read_coverage_config( + self.config = read_coverage_config( config_file=config_file, data_file=data_file, cover_pylib=cover_pylib, timid=timid, branch=branch, parallel=bool_or_none(data_suffix), source=source, run_omit=omit, run_include=include, debug=debug, report_omit=omit, report_include=include, - concurrency=concurrency, + concurrency=concurrency, context=context, ) # This is injectable by tests. self._debug_file = None self._auto_load = self._auto_save = auto_data - self._data_suffix = data_suffix - - # The matchers for _should_trace. - self.source_match = None - self.source_pkgs_match = None - self.pylib_match = self.cover_match = None - self.include_match = self.omit_match = None + self._data_suffix_specified = data_suffix # Is it ok for no data to be collected? self._warn_no_data = True self._warn_unimported_source = True + self._warn_preimported_source = check_preimported # A record of all the warnings that have been issued. self._warnings = [] # Other instance attributes, set later. - self.omit = self.include = self.source = None - self.source_pkgs_unmatched = None - self.source_pkgs = None - self.data = self.data_files = self.collector = None - self.plugins = None - self.pylib_paths = self.cover_paths = None - self.data_suffix = self.run_suffix = None + self._data = self._collector = None + self._plugins = None + self._inorout = None + self._inorout_class = InOrOut + self._data_suffix = self._run_suffix = None self._exclude_re = None - self.debug = None + self._debug = None # State machine variables: # Have we initialized everything? self._inited = False + self._inited_for_start = False # Have we started collecting and not stopped it? self._started = False + # Have we written --debug output? + self._wrote_debug = False # If we have sub-process measurement happening automatically, then we # want any explicit creation of a Coverage object to mean, this process @@ -209,13 +194,7 @@ class Coverage(object): # Create and configure the debugging controller. COVERAGE_DEBUG_FILE # is an environment variable, the name of a file to append debug logs # to. - if self._debug_file is None: - debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE") - if debug_file_name: - self._debug_file = open(debug_file_name, "a") - else: - self._debug_file = sys.stderr - self.debug = DebugControl(self.config.debug, self._debug_file) + self._debug = DebugControl(self.config.debug, self._debug_file) # _exclude_re is a dict that maps exclusion list names to compiled regexes. self._exclude_re = {} @@ -223,364 +202,42 @@ class Coverage(object): set_relative_directory() # Load plugins - self.plugins = Plugins.load_plugins(self.config.plugins, self.config, self.debug) + self._plugins = Plugins.load_plugins(self.config.plugins, self.config, self._debug) # Run configuring plugins. - for plugin in self.plugins.configurers: + for plugin in self._plugins.configurers: # We need an object with set_option and get_option. Either self or # self.config will do. Choosing randomly stops people from doing # other things with those objects, against the public API. Yes, # this is a bit childish. :) plugin.configure([self, self.config][int(time.time()) % 2]) - # The source argument can be directories or package names. - self.source = [] - self.source_pkgs = [] - for src in self.config.source or []: - if os.path.isdir(src): - self.source.append(canonical_filename(src)) - else: - self.source_pkgs.append(src) - self.source_pkgs_unmatched = self.source_pkgs[:] - - self.omit = prep_patterns(self.config.run_omit) - self.include = prep_patterns(self.config.run_include) - - concurrency = self.config.concurrency or [] - if "multiprocessing" in concurrency: - if not patch_multiprocessing: - raise CoverageException( # pragma: only jython - "multiprocessing is not supported on this Python" - ) - patch_multiprocessing(rcfile=self.config_file) - # Multi-processing uses parallel for the subprocesses, so also use - # it for the main process. - self.config.parallel = True - - self.collector = Collector( - should_trace=self._should_trace, - check_include=self._check_include_omit_etc, - timid=self.config.timid, - branch=self.config.branch, - warn=self._warn, - concurrency=concurrency, - ) - - # Early warning if we aren't going to be able to support plugins. - if self.plugins.file_tracers and not self.collector.supports_plugins: - self._warn( - "Plugin file tracers (%s) aren't supported with %s" % ( - ", ".join( - plugin._coverage_plugin_name - for plugin in self.plugins.file_tracers - ), - self.collector.tracer_name(), - ) - ) - for plugin in self.plugins.file_tracers: - plugin._coverage_enabled = False - - # 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 self._data_suffix or self.config.parallel: - if not isinstance(self._data_suffix, string_class): - # if data_suffix=True, use .machinename.pid.random - self._data_suffix = True - else: - self._data_suffix = None - self.data_suffix = None - self.run_suffix = self._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(debug=self.debug) - self.data_files = CoverageDataFiles( - basename=self.config.data_file, warn=self._warn, debug=self.debug, - ) - - # The directories for files considered "installed with the interpreter". - self.pylib_paths = set() - 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, inspect, os, platform, _pypy_irc_topic, re, _structseq, traceback): - if m is not None and hasattr(m, "__file__"): - self.pylib_paths.add(self._canonical_path(m, directory=True)) - - if _structseq and not hasattr(_structseq, '__file__'): - # PyPy 2.4 has no __file__ in the builtin modules, but the code - # objects still have the file names. So dig into one to find - # the path to exclude. - structseq_new = _structseq.structseq_new - try: - structseq_file = structseq_new.func_code.co_filename - except AttributeError: - structseq_file = structseq_new.__code__.co_filename - self.pylib_paths.add(self._canonical_path(structseq_file)) - - # To avoid tracing the coverage.py code itself, we skip anything - # located where we are. - self.cover_paths = [self._canonical_path(__file__, directory=True)] - if env.TESTING: - # Don't include our own test code. - self.cover_paths.append(os.path.join(self.cover_paths[0], "tests")) - - # When testing, we use PyContracts, which should be considered - # part of coverage.py, and it uses six. Exclude those directories - # just as we exclude ourselves. - import contracts - import six - for mod in [contracts, six]: - self.cover_paths.append(self._canonical_path(mod)) - - # Set the reporting precision. - Numbers.set_precision(self.config.precision) - - atexit.register(self._atexit) - - # Create the matchers we need for _should_trace - if self.source or self.source_pkgs: - self.source_match = TreeMatcher(self.source) - self.source_pkgs_match = ModuleMatcher(self.source_pkgs) - else: - if self.cover_paths: - self.cover_match = TreeMatcher(self.cover_paths) - if self.pylib_paths: - self.pylib_match = TreeMatcher(self.pylib_paths) - if self.include: - self.include_match = FnmatchMatcher(self.include) - if self.omit: - self.omit_match = FnmatchMatcher(self.omit) - - # The user may want to debug things, show info if desired. - self._write_startup_debug() + def _post_init(self): + """Stuff to do after everything is initialized.""" + if not self._wrote_debug: + self._wrote_debug = True + self._write_startup_debug() def _write_startup_debug(self): """Write out debug info at startup if needed.""" wrote_any = False - with self.debug.without_callers(): - if self.debug.should('config'): + with self._debug.without_callers(): + if self._debug.should('config'): config_info = sorted(self.config.__dict__.items()) - write_formatted_info(self.debug, "config", config_info) + config_info = [(k, v) for k, v in config_info if not k.startswith('_')] + write_formatted_info(self._debug, "config", config_info) wrote_any = True - if self.debug.should('sys'): - write_formatted_info(self.debug, "sys", self.sys_info()) - for plugin in self.plugins: + if self._debug.should('sys'): + write_formatted_info(self._debug, "sys", self.sys_info()) + for plugin in self._plugins: header = "sys: " + plugin._coverage_plugin_name info = plugin.sys_info() - write_formatted_info(self.debug, header, info) + write_formatted_info(self._debug, header, info) wrote_any = True if wrote_any: - write_formatted_info(self.debug, "end", ()) - - def _canonical_path(self, morf, directory=False): - """Return the canonical path of the module or file `morf`. - - If the module is a package, then return its directory. If it is a - module, then return its file, unless `directory` is True, in which - case return its enclosing directory. - - """ - morf_path = PythonFileReporter(morf, self).filename - if morf_path.endswith("__init__.py") or directory: - morf_path = os.path.split(morf_path)[0] - return morf_path - - def _name_for_module(self, module_globals, filename): - """Get the name of the module for a set of globals and file name. - - For configurability's sake, we allow __main__ modules to be matched by - their importable name. - - If loaded via runpy (aka -m), we can usually recover the "original" - full dotted module name, otherwise, we resort to interpreting the - file name to get the module's name. In the case that the module name - can't be determined, None is returned. - - """ - if module_globals is None: # pragma: only ironpython - # IronPython doesn't provide globals: https://github.com/IronLanguages/main/issues/1296 - module_globals = {} - - dunder_name = module_globals.get('__name__', None) - - if isinstance(dunder_name, str) and dunder_name != '__main__': - # This is the usual case: an imported module. - return dunder_name - - loader = module_globals.get('__loader__', None) - for attrname in ('fullname', 'name'): # attribute renamed in py3.2 - if hasattr(loader, attrname): - fullname = getattr(loader, attrname) - else: - continue - - if isinstance(fullname, str) and fullname != '__main__': - # Module loaded via: runpy -m - return fullname - - # Script as first argument to Python command line. - inspectedname = inspect.getmodulename(filename) - if inspectedname is not None: - return inspectedname - else: - return dunder_name - - def _should_trace_internal(self, filename, frame): - """Decide whether to trace execution in `filename`, with a reason. - - This function is called from the trace function. As each new file name - is encountered, this function determines whether it is traced or not. - - Returns a FileDisposition object. - - """ - original_filename = filename - disp = _disposition_init(self.collector.file_disposition_class, filename) - - def nope(disp, reason): - """Simple helper to make it easy to return NO.""" - disp.trace = False - disp.reason = reason - return disp - - # Compiled Python files have two file names: frame.f_code.co_filename is - # the file name 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 and frame.f_globals.get('__file__') - if dunder_file: - filename = source_for_file(dunder_file) - if original_filename and not original_filename.startswith('<'): - orig = os.path.basename(original_filename) - if orig != os.path.basename(filename): - # Files shouldn't be renamed when moved. This happens when - # exec'ing code. If it seems like something is wrong with - # the frame's file name, then just use the original. - filename = original_filename - - if not filename: - # Empty string is pretty useless. - return nope(disp, "empty string isn't a file name") - - if filename.startswith('memory:'): - return nope(disp, "memory isn't traceable") - - if filename.startswith('<'): - # Lots of non-file execution is represented with artificial - # file names 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 nope(disp, "not a real file name") - - # pyexpat does a dumb thing, calling the trace function explicitly from - # C code with a C file name. - if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename): - return nope(disp, "pyexpat lies about itself") - - # Jython reports the .class file to the tracer, use the source file. - if filename.endswith("$py.class"): - filename = filename[:-9] + ".py" - - canonical = canonical_filename(filename) - disp.canonical_filename = canonical - - # Try the plugins, see if they have an opinion about the file. - plugin = None - for plugin in self.plugins.file_tracers: - if not plugin._coverage_enabled: - continue - - try: - file_tracer = plugin.file_tracer(canonical) - if file_tracer is not None: - file_tracer._coverage_plugin = plugin - disp.trace = True - disp.file_tracer = file_tracer - if file_tracer.has_dynamic_source_filename(): - disp.has_dynamic_filename = True - else: - disp.source_filename = canonical_filename( - file_tracer.source_filename() - ) - break - except Exception: - self._warn( - "Disabling plug-in %r due to an exception:" % ( - plugin._coverage_plugin_name - ) - ) - traceback.print_exc() - plugin._coverage_enabled = False - continue - else: - # No plugin wanted it: it's Python. - disp.trace = True - disp.source_filename = canonical - - if not disp.has_dynamic_filename: - if not disp.source_filename: - raise CoverageException( - "Plugin %r didn't set source_filename for %r" % - (plugin, disp.original_filename) - ) - reason = self._check_include_omit_etc_internal( - disp.source_filename, frame, - ) - if reason: - nope(disp, reason) - - return disp - - def _check_include_omit_etc_internal(self, filename, frame): - """Check a file name against the include, omit, etc, rules. - - Returns a string or None. String means, don't trace, and is the reason - why. None means no reason found to not trace. - - """ - modulename = self._name_for_module(frame.f_globals, 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 self.source_pkgs_match.match(modulename): - if modulename in self.source_pkgs_unmatched: - self.source_pkgs_unmatched.remove(modulename) - elif not self.source_match.match(filename): - return "falls outside the --source trees" - elif self.include_match: - if not self.include_match.match(filename): - return "falls outside the --include trees" - else: - # If we aren't supposed to trace installed code, then check if this - # is near the Python standard library and skip it if so. - if self.pylib_match and self.pylib_match.match(filename): - return "is in the stdlib" - - # We exclude the coverage.py code itself, since a little of it - # will be measured otherwise. - if self.cover_match and self.cover_match.match(filename): - return "is part of coverage.py" - - # Check the file against the omit pattern. - if self.omit_match and self.omit_match.match(filename): - return "is inside an --omit pattern" - - # No reason found to skip this file. - return None + write_formatted_info(self._debug, "end", ()) def _should_trace(self, filename, frame): """Decide whether to trace execution in `filename`. @@ -588,9 +245,9 @@ class Coverage(object): Calls `_should_trace_internal`, and returns the FileDisposition. """ - disp = self._should_trace_internal(filename, frame) - if self.debug.should('trace'): - self.debug.write(_disposition_debug_msg(disp)) + disp = self._inorout.should_trace(filename, frame) + if self._debug.should('trace'): + self._debug.write(disposition_debug_msg(disp)) return disp def _check_include_omit_etc(self, filename, frame): @@ -599,13 +256,13 @@ class Coverage(object): Returns a boolean: True if the file should be traced, False if not. """ - reason = self._check_include_omit_etc_internal(filename, frame) - if self.debug.should('trace'): + reason = self._inorout.check_include_omit_etc(filename, frame) + if self._debug.should('trace'): if not reason: msg = "Including %r" % (filename,) else: msg = "Not including %r: %s" % (filename, reason) - self.debug.write(msg) + self._debug.write(msg) return not reason @@ -621,7 +278,7 @@ class Coverage(object): self._warnings.append(msg) if slug: msg = "%s (%s)" % (msg, slug) - if self.debug.should('pid'): + if self._debug.should('pid'): msg = "[%d] %s" % (os.getpid(), msg) sys.stderr.write("Coverage.py warning: %s\n" % msg) @@ -664,17 +321,97 @@ class Coverage(object): """ self.config.set_option(option_name, value) - def use_cache(self, usecache): - """Obsolete method.""" - self._init() - if not usecache: - self._warn("use_cache(False) is no longer supported.") - def load(self): """Load previously-collected coverage data from the data file.""" self._init() - self.collector.reset() - self.data_files.read(self.data) + if self._collector: + self._collector.reset() + should_skip = self.config.parallel and not os.path.exists(self.config.data_file) + if not should_skip: + self._init_data(suffix=None) + self._post_init() + if not should_skip: + self._data.read() + + def _init_for_start(self): + """Initialization for start()""" + # Construct the collector. + concurrency = self.config.concurrency or [] + if "multiprocessing" in concurrency: + if not patch_multiprocessing: + raise CoverageException( # pragma: only jython + "multiprocessing is not supported on this Python" + ) + patch_multiprocessing(rcfile=self.config.config_file) + # Multi-processing uses parallel for the subprocesses, so also use + # it for the main process. + self.config.parallel = True + + if self.config.dynamic_context is None: + should_start_context = None + elif self.config.dynamic_context == "test_function": + should_start_context = should_start_context_test_function + else: + raise CoverageException( + "Don't understand dynamic_context setting: {!r}".format(self.config.dynamic_context) + ) + + self._collector = Collector( + should_trace=self._should_trace, + check_include=self._check_include_omit_etc, + should_start_context=should_start_context, + timid=self.config.timid, + branch=self.config.branch, + warn=self._warn, + concurrency=concurrency, + ) + + suffix = self._data_suffix_specified + if suffix or self.config.parallel: + if not isinstance(suffix, string_class): + # if data_suffix=True, use .machinename.pid.random + suffix = True + else: + suffix = None + + self._init_data(suffix) + + self._collector.use_data(self._data, self.config.context) + + # Early warning if we aren't going to be able to support plugins. + if self._plugins.file_tracers and not self._collector.supports_plugins: + self._warn( + "Plugin file tracers (%s) aren't supported with %s" % ( + ", ".join( + plugin._coverage_plugin_name + for plugin in self._plugins.file_tracers + ), + self._collector.tracer_name(), + ) + ) + for plugin in self._plugins.file_tracers: + plugin._coverage_enabled = False + + # Create the file classifying substructure. + self._inorout = self._inorout_class(warn=self._warn) + self._inorout.configure(self.config) + self._inorout.plugins = self._plugins + self._inorout.disp_class = self._collector.file_disposition_class + + atexit.register(self._atexit) + + def _init_data(self, suffix): + """Create a data file if we don't have one yet.""" + if self._data is None: + # 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, + suffix=suffix, + warn=self._warn, + debug=self._debug, + ) def start(self): """Start measuring code coverage. @@ -688,29 +425,35 @@ class Coverage(object): """ self._init() - if self.include: - if self.source or self.source_pkgs: - self._warn("--include is ignored because --source is set", slug="include-ignored") - 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 not self._inited_for_start: + self._inited_for_start = True + self._init_for_start() + self._post_init() + + # Issue warnings for possible problems. + self._inorout.warn_conflicting_settings() + + # See if we think some code that would eventually be measured has + # already been imported. + if self._warn_preimported_source: + self._inorout.warn_already_imported_files() + if self._auto_load: self.load() - self.collector.start() + self._collector.start() self._started = True def stop(self): """Stop measuring code coverage.""" if self._started: - self.collector.stop() + self._collector.stop() self._started = False def _atexit(self): """Clean up on process shutdown.""" - if self.debug.should("process"): - self.debug.write("atexit: {0!r}".format(self)) + if self._debug.should("process"): + self._debug.write("atexit: {0!r}".format(self)) if self._started: self.stop() if self._auto_save: @@ -724,9 +467,12 @@ class Coverage(object): """ self._init() - self.collector.reset() - self.data.erase() - self.data_files.erase(parallel=self.config.parallel) + self._post_init() + if self._collector: + self._collector.reset() + self._init_data(suffix=None) + self._data.erase(parallel=self.config.parallel) + self._data = None def clear_exclude(self, which='exclude'): """Clear the exclude list.""" @@ -777,9 +523,8 @@ class Coverage(object): def save(self): """Save the collected coverage data to the data file.""" - self._init() - self.get_data() - self.data_files.write(self.data, suffix=self.data_suffix) + data = self.get_data() + data.write() def combine(self, data_paths=None, strict=False): """Combine together a number of similarly-named coverage data files. @@ -804,6 +549,8 @@ class Coverage(object): """ self._init() + self._init_data(suffix=None) + self._post_init() self.get_data() aliases = None @@ -814,9 +561,7 @@ class Coverage(object): for pattern in paths[1:]: aliases.add(pattern, result) - self.data_files.combine_parallel_data( - self.data, aliases=aliases, data_paths=data_paths, strict=strict, - ) + combine_parallel_data(self._data, aliases=aliases, data_paths=data_paths, strict=strict) def get_data(self): """Get the collected data. @@ -829,11 +574,13 @@ class Coverage(object): """ self._init() + self._init_data(suffix=None) + self._post_init() - if self.collector.save_data(self.data): + if self._collector and self._collector.flush_data(): self._post_save_work() - return self.data + return self._data def _post_save_work(self): """After saving data, look for warnings, post-work, etc. @@ -845,82 +592,18 @@ class Coverage(object): # If there are still entries in the source_pkgs_unmatched list, # then we never encountered those packages. if self._warn_unimported_source: - for pkg in self.source_pkgs_unmatched: - self._warn_about_unmeasured_code(pkg) + self._inorout.warn_unimported_source() # Find out if we got any data. - if not self.data and self._warn_no_data: + if not self._data and self._warn_no_data: self._warn("No data was collected.", slug="no-data-collected") # Find files that were never executed at all. - for pkg in self.source_pkgs: - if (not pkg in sys.modules or - not hasattr(sys.modules[pkg], '__file__') or - not os.path.exists(sys.modules[pkg].__file__)): - continue - pkg_file = source_for_file(sys.modules[pkg].__file__) - self._find_unexecuted_files(self._canonical_path(pkg_file)) - - for src in self.source: - self._find_unexecuted_files(src) + for file_path, plugin_name in self._inorout.find_unexecuted_files(): + self._data.touch_file(file_path, plugin_name) if self.config.note: - self.data.add_run_info(note=self.config.note) - - def _warn_about_unmeasured_code(self, pkg): - """Warn about a package or module that we never traced. - - `pkg` is a string, the name of the package or module. - - """ - mod = sys.modules.get(pkg) - if mod is None: - self._warn("Module %s was never imported." % pkg, slug="module-not-imported") - return - - is_namespace = hasattr(mod, '__path__') and not hasattr(mod, '__file__') - has_file = hasattr(mod, '__file__') and os.path.exists(mod.__file__) - - if is_namespace: - # A namespace package. It's OK for this not to have been traced, - # since there is no code directly in it. - return - - if not has_file: - self._warn("Module %s has no Python source." % pkg, slug="module-not-python") - return - - # The module was in sys.modules, and seems like a module with code, but - # we never measured it. I guess that means it was imported before - # coverage even started. - self._warn( - "Module %s was previously imported, but not measured" % pkg, - slug="module-not-measured", - ) - - def _find_plugin_files(self, src_dir): - """Get executable files from the plugins.""" - for plugin in self.plugins.file_tracers: - for x_file in plugin.find_executable_files(src_dir): - yield x_file, plugin._coverage_plugin_name - - def _find_unexecuted_files(self, src_dir): - """Find unexecuted files in `src_dir`. - - Search for files in `src_dir` that are probably importable, - and add them as unexecuted files in `self.data`. - - """ - py_files = ((py_file, None) for py_file in find_python_files(src_dir)) - plugin_files = self._find_plugin_files(src_dir) - - for file_path, plugin_name in itertools.chain(py_files, plugin_files): - file_path = canonical_filename(file_path) - if self.omit_match and self.omit_match.match(file_path): - # Turns out this file was omitted, so don't pull it back - # in as unexecuted. - continue - self.data.touch_file(file_path, plugin_name) + self._data.add_run_info(note=self.config.note) # Backward compatibility with version 1. def analysis(self, morf): @@ -945,7 +628,6 @@ class Coverage(object): coverage data. """ - self._init() analysis = self._analyze(morf) return ( analysis.filename, @@ -961,11 +643,16 @@ class Coverage(object): Returns an `Analysis` object. """ - self.get_data() + # All reporting comes through here, so do reporting initialization. + self._init() + Numbers.set_precision(self.config.precision) + self._post_init() + + data = self.get_data() if not isinstance(it, FileReporter): it = self._get_file_reporter(it) - return Analysis(self.data, it) + return Analysis(data, it) def _get_file_reporter(self, morf): """Get a FileReporter for a module or file name.""" @@ -974,9 +661,9 @@ class Coverage(object): if isinstance(morf, string_class): abs_morf = abs_file(morf) - plugin_name = self.data.file_tracer(abs_morf) + plugin_name = self._data.file_tracer(abs_morf) if plugin_name: - plugin = self.plugins.get(plugin_name) + plugin = self._plugins.get(plugin_name) if plugin: file_reporter = plugin.file_reporter(abs_morf) @@ -1004,17 +691,13 @@ class Coverage(object): """ if not morfs: - morfs = self.data.measured_files() + morfs = self._data.measured_files() - # Be sure we have a list. - if not isinstance(morfs, (list, tuple)): + # Be sure we have a collection. + if not isinstance(morfs, (list, tuple, set)): morfs = [morfs] - file_reporters = [] - for morf in morfs: - file_reporter = self._get_file_reporter(morf) - file_reporters.append(file_reporter) - + file_reporters = [self._get_file_reporter(morf) for morf in morfs] return file_reporters def report( @@ -1022,21 +705,30 @@ class Coverage(object): file=None, # pylint: disable=redefined-builtin omit=None, include=None, skip_covered=None, ): - """Write a summary report to `file`. + """Write a textual summary report to `file`. Each module in `morfs` is listed, with counts of statements, executed statements, missing statements, and a list of lines missed. + If `show_missing` is true, then details of which lines or branches are + missing will be included in the report. If `ignore_errors` is true, + then a failure while reporting a single file will not stop the entire + report. + + `file` is a file-like object, suitable for writing. + `include` is a list of file name patterns. Files that match will be included in the report. Files matching `omit` will not be included in the report. - If `skip_covered` is True, don't report on files with 100% coverage. + If `skip_covered` is true, don't report on files with 100% coverage. + + All of the arguments default to the settings read from the + :ref:`configuration file <config>`. Returns a float, the total percentage covered. """ - self.get_data() self.config.from_args( ignore_errors=ignore_errors, report_omit=omit, report_include=include, show_missing=show_missing, skip_covered=skip_covered, @@ -1058,7 +750,6 @@ class Coverage(object): See :meth:`report` for other arguments. """ - self.get_data() self.config.from_args( ignore_errors=ignore_errors, report_omit=omit, report_include=include ) @@ -1085,7 +776,6 @@ class Coverage(object): Returns a float, the total percentage covered. """ - self.get_data() self.config.from_args( ignore_errors=ignore_errors, report_omit=omit, report_include=include, html_dir=directory, extra_css=extra_css, html_title=title, @@ -1110,7 +800,6 @@ class Coverage(object): Returns a float, the total percentage covered. """ - self.get_data() self.config.from_args( ignore_errors=ignore_errors, report_omit=omit, report_include=include, xml_output=outfile, @@ -1151,6 +840,7 @@ class Coverage(object): import coverage as covmod self._init() + self._post_init() def plugin_info(plugins): """Make an entry for the sys_info from a list of plug-ins.""" @@ -1165,71 +855,55 @@ class Coverage(object): info = [ ('version', covmod.__version__), ('coverage', covmod.__file__), - ('cover_paths', self.cover_paths), - ('pylib_paths', self.pylib_paths), - ('tracer', self.collector.tracer_name()), - ('plugins.file_tracers', plugin_info(self.plugins.file_tracers)), - ('plugins.configurers', plugin_info(self.plugins.configurers)), - ('config_files', self.config.attempted_config_files), - ('configs_read', self.config.config_files), - ('data_path', self.data_files.filename), + ('tracer', self._collector.tracer_name() if self._collector else "-none-"), + ('CTracer', 'available' if CTracer else "unavailable"), + ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)), + ('plugins.configurers', plugin_info(self._plugins.configurers)), + ('configs_attempted', self.config.attempted_config_files), + ('configs_read', self.config.config_files_read), + ('config_file', self.config.config_file), + ('config_contents', + repr(self.config._config_contents) + if self.config._config_contents + else '-none-' + ), + ('data_file', self._data.filename if self._data else "-none-"), ('python', sys.version.replace('\n', '')), ('platform', platform.platform()), ('implementation', platform.python_implementation()), ('executable', sys.executable), + ('pid', os.getpid()), ('cwd', os.getcwd()), ('path', sys.path), ('environment', sorted( ("%s = %s" % (k, v)) for k, v in iitems(os.environ) - if k.startswith(("COV", "PY")) + if any(slug in k for slug in ("COV", "PY")) )), - ('command_line', " ".join(getattr(sys, 'argv', ['???']))), + ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))), ] - matcher_names = [ - 'source_match', 'source_pkgs_match', - 'include_match', 'omit_match', - 'cover_match', 'pylib_match', - ] - - for matcher_name in matcher_names: - matcher = getattr(self, matcher_name) - if matcher: - matcher_info = matcher.info() - else: - matcher_info = '-none-' - info.append((matcher_name, matcher_info)) + if self._inorout: + info.extend(self._inorout.sys_info()) return info -# FileDisposition "methods": FileDisposition is a pure value object, so it can -# be implemented in either C or Python. Acting on them is done with these -# functions. +# Mega debugging... +if int(os.environ.get("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging + from coverage.debug import decorate_methods, show_calls -def _disposition_init(cls, original_filename): - """Construct and initialize a new FileDisposition object.""" - disp = cls() - disp.original_filename = original_filename - disp.canonical_filename = original_filename - disp.source_filename = None - disp.trace = False - disp.reason = "" - disp.file_tracer = None - disp.has_dynamic_filename = False - return disp + Coverage = decorate_methods(show_calls(show_args=True), butnot=['get_data'])(Coverage) -def _disposition_debug_msg(disp): - """Make a nice debug message of what the FileDisposition is doing.""" - if disp.trace: - msg = "Tracing %r" % (disp.original_filename,) - if disp.file_tracer: - msg += ": will be traced by %r" % disp.file_tracer - else: - msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason) - return msg +def should_start_context_test_function(frame): + """Who-Tests-What hack: Determine whether this frame begins a new who-context.""" + with open("/tmp/ssc.txt", "a") as f: + f.write("hello\n") + fn_name = frame.f_code.co_name + if fn_name.startswith("test"): + return fn_name + return None def process_startup(): @@ -1277,10 +951,11 @@ def process_startup(): cov = Coverage(config_file=cps) process_startup.coverage = cov - cov.start() cov._warn_no_data = False cov._warn_unimported_source = False + cov._warn_preimported_source = False cov._auto_save = True + cov.start() return cov diff --git a/coverage/ctracer/datastack.c b/coverage/ctracer/datastack.c index 515ba924..a9cfcc2c 100644 --- a/coverage/ctracer/datastack.c +++ b/coverage/ctracer/datastack.c @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #include "util.h" #include "datastack.h" diff --git a/coverage/ctracer/datastack.h b/coverage/ctracer/datastack.h index b2dbeb95..3b3078ba 100644 --- a/coverage/ctracer/datastack.h +++ b/coverage/ctracer/datastack.h @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #ifndef _COVERAGE_DATASTACK_H #define _COVERAGE_DATASTACK_H diff --git a/coverage/ctracer/filedisp.c b/coverage/ctracer/filedisp.c index 479a2c9f..47782ae0 100644 --- a/coverage/ctracer/filedisp.c +++ b/coverage/ctracer/filedisp.c @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #include "util.h" #include "filedisp.h" diff --git a/coverage/ctracer/filedisp.h b/coverage/ctracer/filedisp.h index ada68eaf..860f9a50 100644 --- a/coverage/ctracer/filedisp.h +++ b/coverage/ctracer/filedisp.h @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #ifndef _COVERAGE_FILEDISP_H #define _COVERAGE_FILEDISP_H diff --git a/coverage/ctracer/module.c b/coverage/ctracer/module.c index 76231859..f308902b 100644 --- a/coverage/ctracer/module.c +++ b/coverage/ctracer/module.c @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #include "util.h" #include "tracer.h" diff --git a/coverage/ctracer/stats.h b/coverage/ctracer/stats.h index c5ffdf5f..05173369 100644 --- a/coverage/ctracer/stats.h +++ b/coverage/ctracer/stats.h @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #ifndef _COVERAGE_STATS_H #define _COVERAGE_STATS_H diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c index 6dcdc576..7d639112 100644 --- a/coverage/ctracer/tracer.c +++ b/coverage/ctracer/tracer.c @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ /* C-based Tracer for coverage.py. */ @@ -341,7 +341,6 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) CFileDisposition * pdisp = NULL; STATS( self->stats.calls++; ) - self->activity = TRUE; /* Grow the stack. */ if (CTracer_set_pdata_stack(self) < 0) { @@ -353,7 +352,7 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth]; /* See if this frame begins a new context. */ - if (self->should_start_context && self->context == Py_None) { + if (self->should_start_context != Py_None && self->context == Py_None) { PyObject * context; /* We're looking for our context, ask should_start_context if this is the start. */ STATS( self->stats.start_context_calls++; ) @@ -866,6 +865,8 @@ CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unuse goto error; } + self->activity = TRUE; + switch (what) { case PyTrace_CALL: if (CTracer_handle_call(self, frame) < 0) { diff --git a/coverage/ctracer/tracer.h b/coverage/ctracer/tracer.h index d5d630fb..a83742dd 100644 --- a/coverage/ctracer/tracer.h +++ b/coverage/ctracer/tracer.h @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #ifndef _COVERAGE_TRACER_H #define _COVERAGE_TRACER_H @@ -27,7 +27,6 @@ typedef struct CTracer { PyObject * trace_arcs; PyObject * should_start_context; PyObject * switch_context; - PyObject * context; /* Has the tracer been started? */ BOOL started; @@ -35,6 +34,8 @@ typedef struct CTracer { BOOL tracing_arcs; /* Have we had any activity? */ BOOL activity; + /* The current dynamic context. */ + PyObject * context; /* The data stack is a stack of dictionaries. Each dictionary collects diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h index f0c302cf..96d2e51c 100644 --- a/coverage/ctracer/util.h +++ b/coverage/ctracer/util.h @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ #ifndef _COVERAGE_UTIL_H #define _COVERAGE_UTIL_H diff --git a/coverage/data.py b/coverage/data.py index 6f76a727..e6d56d84 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Coverage data for coverage.py.""" @@ -15,14 +15,24 @@ import socket from coverage import env from coverage.backward import iitems, string_class -from coverage.debug import _TEST_NAME_FILE from coverage.files import PathAliases from coverage.misc import CoverageException, file_be_gone, isolate_module os = isolate_module(os) -class CoverageData(object): +def filename_suffix(suffix): + if 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. + dice = random.Random(os.urandom(8)).randint(0, 999999) + suffix = "%s.%s.%06d" % (socket.gethostname(), os.getpid(), dice) + return suffix + + +class CoverageJsonData(object): """Manages collected coverage data, including file storage. This class is the public supported API to the data coverage.py collects @@ -57,8 +67,10 @@ class CoverageData(object): names in this API are case-sensitive, even on platforms with case-insensitive file systems. - To read a coverage.py data file, use :meth:`read_file`, or - :meth:`read_fileobj` if you have an already-opened file. You can then + A data file is associated with the data when the :class:`CoverageData` + is created. + + To read a coverage.py data file, use :meth:`read`. You can then access the line, arc, or file tracer data with :meth:`lines`, :meth:`arcs`, or :meth:`file_tracer`. Run information is available with :meth:`run_infos`. @@ -69,17 +81,15 @@ class CoverageData(object): most Python containers, you can determine if there is any data at all by using this object as a boolean value. - Most data files will be created by coverage.py itself, but you can use methods here to create data files if you like. The :meth:`add_lines`, :meth:`add_arcs`, and :meth:`add_file_tracers` methods add data, in ways that are convenient for coverage.py. The :meth:`add_run_info` method adds key-value pairs to the run information. - To add a file without any measured data, use :meth:`touch_file`. + To add a source file without any measured data, use :meth:`touch_file`. - You write to a named file with :meth:`write_file`, or to an already opened - file with :meth:`write_fileobj`. + Write the data to its file with :meth:`write`. You can clear the data in memory with :meth:`erase`. Two data collections can be combined by using :meth:`update` on one :class:`CoverageData`, @@ -112,13 +122,20 @@ class CoverageData(object): # line data is easily recovered from the arcs: it is all the first elements # of the pairs that are greater than zero. - def __init__(self, debug=None): + def __init__(self, basename=None, suffix=None, warn=None, debug=None): """Create a CoverageData. + `warn` is the warning function to use. + + `basename` is the name of the file to use for storing data. + `debug` is a `DebugControl` object for writing debug messages. """ + self._warn = warn self._debug = debug + self.filename = os.path.abspath(basename or ".coverage") + self.suffix = suffix # A map from canonical Python source file name to a dictionary in # which there's an entry for each line number that has been @@ -235,34 +252,24 @@ class CoverageData(object): return self._runs def measured_files(self): - """A list of all files that had been measured.""" - return list(self._arcs or self._lines or {}) - - def line_counts(self, fullpath=False): - """Return a dict summarizing the line coverage data. - - Keys are based on the file names, 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. - - Returns a dict mapping file names to counts of lines. - - """ - summ = {} - if fullpath: - filename_fn = lambda f: f - else: - filename_fn = os.path.basename - for filename in self.measured_files(): - summ[filename_fn(filename)] = len(self.lines(filename)) - return summ + """A set of all files that had been measured.""" + return set(self._arcs or self._lines or {}) def __nonzero__(self): return bool(self._lines or self._arcs) __bool__ = __nonzero__ - def read_fileobj(self, file_obj): + def read(self): + """Read the coverage data. + + It is fine for the file to not exist, in which case no data is read. + + """ + if os.path.exists(self.filename): + self._read_file(self.filename) + + def _read_fileobj(self, file_obj): """Read the coverage data from the given file object. Should only be used on an empty CoverageData object. @@ -284,13 +291,13 @@ class CoverageData(object): self._validate() - def read_file(self, filename): + def _read_file(self, filename): """Read the coverage data from `filename` into this object.""" if self._debug and self._debug.should('dataio'): self._debug.write("Reading data from %r" % (filename,)) try: with self._open_for_reading(filename) as f: - self.read_fileobj(f) + self._read_fileobj(f) except Exception as exc: raise CoverageException( "Couldn't read data from '%s': %s: %s" % ( @@ -438,7 +445,27 @@ class CoverageData(object): self._validate() - def write_fileobj(self, file_obj): + def set_context(self, context): + """Set the context. Not implemented for JSON storage.""" + if context: + raise CoverageException("JSON storage doesn't support contexts") + + def write(self): + """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. + + """ + filename = self.filename + suffix = filename_suffix(self.suffix) + if suffix: + filename += "." + suffix + self._write_file(filename) + + def _write_fileobj(self, file_obj): """Write the coverage data to `file_obj`.""" # Create the file data. @@ -460,21 +487,38 @@ class CoverageData(object): file_obj.write(self._GO_AWAY) json.dump(file_data, file_obj, separators=(',', ':')) - def write_file(self, filename): + def _write_file(self, filename): """Write the coverage data to `filename`.""" if self._debug and self._debug.should('dataio'): self._debug.write("Writing data to %r" % (filename,)) with open(filename, 'w') as fdata: - self.write_fileobj(fdata) + self._write_fileobj(fdata) + + def erase(self, parallel=False): + """Erase the data in this object. - def erase(self): - """Erase the data in this object.""" + If `parallel` is true, then also deletes data files created from the + basename by parallel-mode. + + """ self._lines = None self._arcs = None self._file_tracers = {} self._runs = [] self._validate() + if self._debug and self._debug.should('dataio'): + self._debug.write("Erasing data file %r" % (self.filename,)) + file_be_gone(self.filename) + if parallel: + data_dir, local = os.path.split(self.filename) + localdot = local + '.*' + pattern = os.path.join(os.path.abspath(data_dir), localdot) + for filename in glob.glob(pattern): + if self._debug and self._debug.should('dataio'): + self._debug.write("Erasing parallel data file %r" % (filename,)) + file_be_gone(filename) + def update(self, other_data, aliases=None): """Update this data with data from another `CoverageData`. @@ -582,20 +626,6 @@ class CoverageData(object): for key in val: assert isinstance(key, string_class), "Key in _runs shouldn't be %r" % (key,) - def add_to_hash(self, filename, hasher): - """Contribute `filename`'s data to the `hasher`. - - `hasher` is a `coverage.misc.Hasher` instance to be updated with - the file's data. It should only get the results data, not the run - data. - - """ - if self._has_arcs(): - hasher.update(sorted(self.arcs(filename) or [])) - else: - hasher.update(sorted(self.lines(filename) or [])) - hasher.update(self.file_tracer(filename)) - ## ## Internal ## @@ -609,139 +639,113 @@ class CoverageData(object): return self._arcs is not None -class CoverageDataFiles(object): - """Manage the use of coverage data files.""" +STORAGE = os.environ.get("COVERAGE_STORAGE", "sql") +if STORAGE == "json": + CoverageData = CoverageJsonData +elif STORAGE == "sql": + from coverage.sqldata import CoverageSqliteData + CoverageData = CoverageSqliteData - def __init__(self, basename=None, warn=None, debug=None): - """Create a CoverageDataFiles to manage data files. - `warn` is the warning function to use. +def line_counts(data, fullpath=False): + """Return a dict summarizing the line coverage data. - `basename` is the name of the file to use for storing data. + Keys are based on the file names, 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. - `debug` is a `DebugControl` object for writing debug messages. + Returns a dict mapping file names to counts of lines. - """ - self.warn = warn - self.debug = debug - - # Construct the file name that will be used for data storage. - self.filename = os.path.abspath(basename or ".coverage") - - def erase(self, parallel=False): - """Erase the data from the file storage. - - If `parallel` is true, then also deletes data files created from the - basename by parallel-mode. + """ + summ = {} + if fullpath: + filename_fn = lambda f: f + else: + filename_fn = os.path.basename + for filename in data.measured_files(): + summ[filename_fn(filename)] = len(data.lines(filename)) + return summ - """ - if self.debug and self.debug.should('dataio'): - self.debug.write("Erasing data file %r" % (self.filename,)) - file_be_gone(self.filename) - if parallel: - data_dir, local = os.path.split(self.filename) - localdot = local + '.*' - pattern = os.path.join(os.path.abspath(data_dir), localdot) - for filename in glob.glob(pattern): - if self.debug and self.debug.should('dataio'): - self.debug.write("Erasing parallel data file %r" % (filename,)) - file_be_gone(filename) - def read(self, data): - """Read the coverage data.""" - if os.path.exists(self.filename): - data.read_file(self.filename) +def add_data_to_hash(data, filename, hasher): + """Contribute `filename`'s data to the `hasher`. - def write(self, data, suffix=None): - """Write the collected coverage data to a file. + `hasher` is a `coverage.misc.Hasher` instance to be updated with + the file's data. It should only get the results data, not the run + data. - `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 data.has_arcs(): + hasher.update(sorted(data.arcs(filename) or [])) + else: + hasher.update(sorted(data.lines(filename) or [])) + hasher.update(data.file_tracer(filename)) - """ - filename = self.filename - if 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: # pragma: debugging - with open(_TEST_NAME_FILE) as f: - test_name = f.read() - extra = "." + test_name - dice = random.Random(os.urandom(8)).randint(0, 999999) - suffix = "%s%s.%s.%06d" % (socket.gethostname(), extra, os.getpid(), dice) - if suffix: - filename += "." + suffix - data.write_file(filename) +def combine_parallel_data(data, aliases=None, data_paths=None, strict=False): + """Combine a number of data files together. - def combine_parallel_data(self, data, aliases=None, data_paths=None, strict=False): - """Combine a number of data files together. + Treat `data.filename` as a file prefix, and combine the data from all + of the data files starting with that prefix plus a dot. - 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. - If `aliases` is provided, it's a `PathAliases` object that is used to - re-map paths to match the local machine's. + If `data_paths` is provided, it is a list of directories or files to + combine. Directories are searched for files that start with + `data.filename` plus dot as a prefix, and those files are combined. - If `data_paths` is provided, it is a list of directories or files to - combine. Directories are searched for files that start with - `self.filename` plus dot as a prefix, and those files are combined. + If `data_paths` is not provided, then the directory portion of + `data.filename` is used as the directory to search for data files. - If `data_paths` is not provided, then the directory portion of - `self.filename` is used as the directory to search for data files. + Every data file found and combined is then deleted from disk. If a file + cannot be read, a warning will be issued, and the file will not be + deleted. - Every data file found and combined is then deleted from disk. If a file - cannot be read, a warning will be issued, and the file will not be - deleted. + If `strict` is true, and no files are found to combine, an error is + raised. - If `strict` is true, and no files are found to combine, an error is - raised. + """ + # Because of the os.path.abspath in the constructor, data_dir will + # never be an empty string. + data_dir, local = os.path.split(data.filename) + localdot = local + '.*' + + data_paths = data_paths or [data_dir] + files_to_combine = [] + for p in data_paths: + if os.path.isfile(p): + files_to_combine.append(os.path.abspath(p)) + elif os.path.isdir(p): + pattern = os.path.join(os.path.abspath(p), localdot) + files_to_combine.extend(glob.glob(pattern)) + else: + raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,)) - """ - # Because of the os.path.abspath in the constructor, data_dir will - # never be an empty string. - data_dir, local = os.path.split(self.filename) - localdot = local + '.*' - - data_paths = data_paths or [data_dir] - files_to_combine = [] - for p in data_paths: - if os.path.isfile(p): - files_to_combine.append(os.path.abspath(p)) - elif os.path.isdir(p): - pattern = os.path.join(os.path.abspath(p), localdot) - files_to_combine.extend(glob.glob(pattern)) - else: - raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,)) - - if strict and not files_to_combine: - raise CoverageException("No data to combine") - - files_combined = 0 - for f in files_to_combine: - new_data = CoverageData(debug=self.debug) - try: - new_data.read_file(f) - except CoverageException as exc: - if self.warn: - # The CoverageException has the file name in it, so just - # use the message as the warning. - self.warn(str(exc)) - else: - data.update(new_data, aliases=aliases) - files_combined += 1 - if self.debug and self.debug.should('dataio'): - self.debug.write("Deleting combined data file %r" % (f,)) - file_be_gone(f) - - if strict and not files_combined: - raise CoverageException("No usable data files") + if strict and not files_to_combine: + raise CoverageException("No data to combine") + files_combined = 0 + for f in files_to_combine: + if data._debug and data._debug.should('dataio'): + data._debug.write("Combining data file %r" % (f,)) + try: + new_data = CoverageData(f, debug=data._debug) + new_data.read() + except CoverageException as exc: + if data._warn: + # The CoverageException has the file name in it, so just + # use the message as the warning. + data._warn(str(exc)) + else: + data.update(new_data, aliases=aliases) + files_combined += 1 + if data._debug and data._debug.should('dataio'): + data._debug.write("Deleting combined data file %r" % (f,)) + file_be_gone(f) + + if strict and not files_combined: + raise CoverageException("No usable data files") def canonicalize_json_data(data): """Canonicalize our JSON data so it can be compared.""" diff --git a/coverage/debug.py b/coverage/debug.py index e68736f6..9077a3af 100644 --- a/coverage/debug.py +++ b/coverage/debug.py @@ -1,12 +1,13 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Control of and utilities for debugging.""" import contextlib +import functools import inspect +import itertools import os -import re import sys try: import _thread @@ -24,27 +25,26 @@ os = isolate_module(os) # This is a list of forced debugging options. FORCED_DEBUG = [] -# A hack for debugging testing in sub-processes. -_TEST_NAME_FILE = "" # "/tmp/covtest.txt" - class DebugControl(object): """Control and output for debugging.""" + show_repr_attr = False # For SimpleReprMixin + def __init__(self, options, output): """Configure the options and output file for debugging.""" self.options = list(options) + FORCED_DEBUG - self.raw_output = output self.suppress_callers = False filters = [] if self.should('pid'): filters.append(add_pid_and_tid) - self.output = DebugOutputFile( - self.raw_output, + self.output = DebugOutputFile.get_one( + output, show_process=self.should('process'), filters=filters, ) + self.raw_output = self.output.outfile def __repr__(self): return "<DebugControl options=%r raw_output=%r>" % (self.options, self.raw_output) @@ -71,6 +71,10 @@ class DebugControl(object): `msg` is the line to write. A newline will be appended. """ + if self.should('self'): + caller_self = inspect.stack()[1][0].f_locals.get('self') + if caller_self is not None: + msg = "[self: {!r}] {}".format(caller_self, msg) self.output.write(msg+"\n") if self.should('callers'): dump_stack_frames(out=self.output, skip=1) @@ -167,6 +171,20 @@ def add_pid_and_tid(text): return text +class SimpleReprMixin(object): + """A mixin implementing a simple __repr__.""" + def __repr__(self): + show_attrs = ( + (k, v) for k, v in self.__dict__.items() + if getattr(v, "show_repr_attr", True) + ) + return "<{klass} @0x{id:x} {attrs}>".format( + klass=self.__class__.__name__, + id=id(self), + attrs=" ".join("{}={!r}".format(k, v) for k, v in show_attrs), + ) + + def filter_text(text, filters): """Run `text` through a series of filters. @@ -215,26 +233,40 @@ class DebugOutputFile(object): # pragma: debugging self.write("New process: executable: %s\n" % (sys.executable,)) self.write("New process: cmd: %s\n" % (cmd,)) if hasattr(os, 'getppid'): - self.write("New process: parent pid: %s\n" % (os.getppid(),)) + self.write("New process: pid: %s, parent pid: %s\n" % (os.getpid(), os.getppid())) SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one' @classmethod - def the_one(cls, fileobj=None, show_process=True, filters=()): - """Get the process-wide singleton DebugOutputFile. + def get_one(cls, fileobj=None, show_process=True, filters=()): + """Get a DebugOutputFile. + + If `fileobj` is provided, then a new DebugOutputFile is made with it. + + If `fileobj` isn't provided, then a file is chosen + (COVERAGE_DEBUG_FILE, or stderr), and a process-wide singleton + DebugOutputFile is made. - If it doesn't exist yet, then create it as a wrapper around the file - object `fileobj`. `show_process` controls whether the debug file adds - process-level information. + `show_process` controls whether the debug file adds process-level + information, and filters is a list of other message filters to apply. """ + if fileobj is not None: + # Make DebugOutputFile around the fileobj passed. + return cls(fileobj, show_process, filters) + # Because of the way igor.py deletes and re-imports modules, # this class can be defined more than once. But we really want # a process-wide singleton. So stash it in sys.modules instead of # on a class attribute. Yes, this is aggressively gross. the_one = sys.modules.get(cls.SYS_MOD_NAME) if the_one is None: - assert fileobj is not None + if fileobj is None: + debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE") + if debug_file_name: + fileobj = open(debug_file_name, "a") + else: + fileobj = sys.stderr sys.modules[cls.SYS_MOD_NAME] = the_one = cls(fileobj, show_process, filters) return the_one @@ -250,46 +282,72 @@ class DebugOutputFile(object): # pragma: debugging def log(msg, stack=False): # pragma: debugging """Write a log message as forcefully as possible.""" - out = DebugOutputFile.the_one() + out = DebugOutputFile.get_one() out.write(msg+"\n") if stack: dump_stack_frames(out=out, skip=1) -def filter_aspectlib_frames(text): # pragma: debugging - """Aspectlib prints stack traces, but includes its own frames. Scrub those out.""" - # <<< aspectlib/__init__.py:257:function_wrapper < igor.py:143:run_tests < ... - text = re.sub(r"(?<= )aspectlib/[^.]+\.py:\d+:\w+ < ", "", text) - return text - - -def enable_aspectlib_maybe(): # pragma: debugging - """For debugging, we can use aspectlib to trace execution. - - Define COVERAGE_ASPECTLIB to enable and configure aspectlib to trace - execution:: - - $ export COVERAGE_LOG=covaspect.txt - $ export COVERAGE_ASPECTLIB=coverage.Coverage:coverage.data.CoverageData - $ coverage run blah.py ... - - This will trace all the public methods on Coverage and CoverageData, - writing the information to covaspect.txt. - - """ - aspects = os.environ.get("COVERAGE_ASPECTLIB", "") - if not aspects: - return - - import aspectlib # pylint: disable=import-error - import aspectlib.debug # pylint: disable=import-error - - filename = os.environ.get("COVERAGE_LOG", "/tmp/covlog.txt") - filters = [add_pid_and_tid, filter_aspectlib_frames] - aspects_file = DebugOutputFile.the_one(open(filename, "a"), show_process=True, filters=filters) - aspect_log = aspectlib.debug.log( - print_to=aspects_file, attributes=['id'], stacktrace=30, use_logging=False - ) - public_methods = re.compile(r'^(__init__|[a-zA-Z].*)$') - for aspect in aspects.split(':'): - aspectlib.weave(aspect, aspect_log, methods=public_methods) +def decorate_methods(decorator, butnot=()): # pragma: debugging + """A class decorator to apply a decorator to public methods.""" + def _decorator(cls): + for name, meth in inspect.getmembers(cls, inspect.isroutine): + public = name == '__init__' or not name.startswith("_") + decorate_it = public and name not in butnot + if decorate_it: + setattr(cls, name, decorator(meth)) + return cls + return _decorator + + +def break_in_pudb(func): # pragma: debugging + """A function decorator to stop in the debugger for each call.""" + @functools.wraps(func) + def _wrapper(*args, **kwargs): + import pudb # pylint: disable=import-error + sys.stdout = sys.__stdout__ + pudb.set_trace() + return func(*args, **kwargs) + return _wrapper + + +OBJ_IDS = itertools.count() +CALLS = itertools.count() +OBJ_ID_ATTR = "$coverage.object_id" + +def show_calls(show_args=True, show_stack=False): # pragma: debugging + """A method decorator to debug-log each call to the function.""" + def _decorator(func): + @functools.wraps(func) + def _wrapper(self, *args, **kwargs): + oid = getattr(self, OBJ_ID_ATTR, None) + if oid is None: + oid = "{:08d} {:04d}".format(os.getpid(), next(OBJ_IDS)) + setattr(self, OBJ_ID_ATTR, oid) + extra = "" + if show_args: + eargs = ", ".join(map(repr, args)) + ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items()) + extra += "(" + extra += eargs + if eargs and ekwargs: + extra += ", " + extra += ekwargs + extra += ")" + if show_stack: + extra += " @ " + extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines()) + msg = "{} {:04d} {}{}\n".format(oid, next(CALLS), func.__name__, extra) + DebugOutputFile.get_one().write(msg) + return func(self, *args, **kwargs) + return _wrapper + return _decorator + + +def _clean_stack_line(s): # pragma: debugging + """Simplify some paths in a stack trace, for compactness.""" + s = s.strip() + s = s.replace(os.path.dirname(__file__) + '/', '') + s = s.replace(os.path.dirname(os.__file__) + '/', '') + s = s.replace(sys.prefix + '/', '') + return s diff --git a/coverage/disposition.py b/coverage/disposition.py new file mode 100644 index 00000000..9b9a997d --- /dev/null +++ b/coverage/disposition.py @@ -0,0 +1,37 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Simple value objects for tracking what to do with files.""" + + +class FileDisposition(object): + """A simple value type for recording what to do with a file.""" + pass + + +# FileDisposition "methods": FileDisposition is a pure value object, so it can +# be implemented in either C or Python. Acting on them is done with these +# functions. + +def disposition_init(cls, original_filename): + """Construct and initialize a new FileDisposition object.""" + disp = cls() + disp.original_filename = original_filename + disp.canonical_filename = original_filename + disp.source_filename = None + disp.trace = False + disp.reason = "" + disp.file_tracer = None + disp.has_dynamic_filename = False + return disp + + +def disposition_debug_msg(disp): + """Make a nice debug message of what the FileDisposition is doing.""" + if disp.trace: + msg = "Tracing %r" % (disp.original_filename,) + if disp.file_tracer: + msg += ": will be traced by %r" % disp.file_tracer + else: + msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason) + return msg diff --git a/coverage/env.py b/coverage/env.py index 4699a1e5..e35d026b 100644 --- a/coverage/env.py +++ b/coverage/env.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Determine facts about the environment.""" diff --git a/coverage/execfile.py b/coverage/execfile.py index 42e0d96a..b2b78444 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Execute files of Python code.""" @@ -9,6 +9,7 @@ import struct import sys import types +from coverage import env from coverage.backward import BUILTINS from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module @@ -111,7 +112,15 @@ def run_python_module(modulename, args): pathname = os.path.abspath(pathname) args[0] = pathname - run_python_file(pathname, args, package=packagename, modulename=modulename, path0="") + # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It + # used to be an empty string (meaning the current directory). It changed + # to be the actual path to the current directory, so that os.chdir wouldn't + # affect the outcome. + if env.PYVERSION >= (3, 7, 0, 'beta', 3): + path0 = os.getcwd() + else: + path0 = "" + run_python_file(pathname, args, package=packagename, modulename=modulename, path0=path0) def run_python_file(filename, args, package=None, modulename=None, path0=None): @@ -128,7 +137,7 @@ def run_python_file(filename, args, package=None, modulename=None, path0=None): function will decide on a value. """ - if modulename is None and sys.version_info >= (3, 3): + if modulename is None and env.PYVERSION >= (3, 3): modulename = '__main__' # Create a module to serve as __main__ @@ -255,7 +264,7 @@ def make_code_from_pyc(filename): raise NoCode("Bad magic number in .pyc file") date_based = True - if sys.version_info >= (3, 7, 0, 'alpha', 4): + if env.PYVERSION >= (3, 7, 0, 'alpha', 4): flags = struct.unpack('<L', fpyc.read(4))[0] hash_based = flags & 0x01 if hash_based: @@ -264,7 +273,7 @@ def make_code_from_pyc(filename): if date_based: # Skip the junk in the header that we don't need. fpyc.read(4) # Skip the moddate. - if sys.version_info >= (3, 3): + if env.PYVERSION >= (3, 3): # 3.3 added another long to the header (size), skip it. fpyc.read(4) diff --git a/coverage/files.py b/coverage/files.py index 759ec2c9..b328f653 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """File wrangling.""" @@ -122,7 +122,9 @@ if env.WINDOWS: else: try: files = os.listdir(head) - except OSError: + except Exception: + # This will raise OSError, or this bizarre TypeError: + # https://bugs.python.org/issue1776160 files = [] _ACTUAL_PATH_LIST_CACHE[head] = files normtail = os.path.normcase(tail) @@ -260,19 +262,8 @@ class ModuleMatcher(object): class FnmatchMatcher(object): """A matcher for files by file name pattern.""" def __init__(self, pats): - self.pats = pats[:] - # fnmatch is platform-specific. On Windows, it does the Windows thing - # of treating / and \ as equivalent. But on other platforms, we need to - # take care of that ourselves. - fnpats = (fnmatch.translate(p) for p in pats) - # Python3.7 fnmatch translates "/" as "/", before that, it translates as "\/", - # so we have to deal with maybe a backslash. - fnpats = (re.sub(r"\\?/", r"[\\\\/]", p) for p in fnpats) - flags = 0 - if env.WINDOWS: - # Windows is also case-insensitive, so make the regex case-insensitive. - flags |= re.IGNORECASE - self.re = re.compile(join_regex(fnpats), flags=flags) + self.pats = list(pats) + self.re = fnmatches_to_regex(self.pats, case_insensitive=env.WINDOWS) def __repr__(self): return "<FnmatchMatcher %r>" % self.pats @@ -296,6 +287,39 @@ def sep(s): return the_sep +def fnmatches_to_regex(patterns, case_insensitive=False, partial=False): + """Convert fnmatch patterns to a compiled regex that matches any of them. + + Slashes are always converted to match either slash or backslash, for + Windows support, even when running elsewhere. + + If `partial` is true, then the pattern will match if the target string + starts with the pattern. Otherwise, it must match the entire string. + + Returns: a compiled regex object. Use the .match method to compare target + strings. + + """ + regexes = (fnmatch.translate(pattern) for pattern in patterns) + # Python3.7 fnmatch translates "/" as "/". Before that, it translates as "\/", + # so we have to deal with maybe a backslash. + regexes = (re.sub(r"\\?/", r"[\\\\/]", regex) for regex in regexes) + + if partial: + # fnmatch always adds a \Z to match the whole string, which we don't + # want, so we remove the \Z. While removing it, we only replace \Z if + # followed by paren (introducing flags), or at end, to keep from + # destroying a literal \Z in the pattern. + regexes = (re.sub(r'\\Z(\(\?|$)', r'\1', regex) for regex in regexes) + + flags = 0 + if case_insensitive: + flags |= re.IGNORECASE + compiled = re.compile(join_regex(regexes), flags=flags) + + return compiled + + class PathAliases(object): """A collection of aliases for paths. @@ -343,18 +367,8 @@ class PathAliases(object): if not pattern.endswith(pattern_sep): pattern += pattern_sep - # Make a regex from the pattern. fnmatch always adds a \Z to - # match the whole string, which we don't want, so we remove the \Z. - # While removing it, we only replace \Z if followed by paren, or at - # end, to keep from destroying a literal \Z in the pattern. - regex_pat = fnmatch.translate(pattern) - regex_pat = re.sub(r'\\Z(\(|$)', r'\1', regex_pat) - - # We want */a/b.py to match on Windows too, 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) + # Make a regex from the pattern. + regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True) # Normalize the result: it must end with a path separator. result_sep = sep(result) diff --git a/coverage/fullcoverage/encodings.py b/coverage/fullcoverage/encodings.py index 699f3863..aeb416e4 100644 --- a/coverage/fullcoverage/encodings.py +++ b/coverage/fullcoverage/encodings.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Imposter encodings module that installs a coverage-style tracer. diff --git a/coverage/html.py b/coverage/html.py index b0c61649..bb519254 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -1,16 +1,18 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """HTML reporting for coverage.py.""" import datetime import json import os +import re import shutil import coverage from coverage import env from coverage.backward import iitems +from coverage.data import add_data_to_hash from coverage.files import flat_rootname from coverage.misc import CoverageException, file_be_gone, Hasher, isolate_module from coverage.report import Reporter @@ -66,6 +68,7 @@ def read_data(fname): def write_html(fname, html): """Write `html` to `fname`, properly encoded.""" + html = re.sub(r"(\A\s+)|(\s+$)", "", html, flags=re.MULTILINE) + "\n" with open(fname, "wb") as fout: fout.write(html.encode('ascii', 'xmlcharrefreplace')) @@ -102,11 +105,11 @@ class HtmlReporter(Reporter): } self.source_tmpl = Templite(read_data("pyfile.html"), self.template_globals) - self.coverage = cov + self.data = cov.get_data() self.files = [] self.all_files_nums = [] - self.has_arcs = self.coverage.data.has_arcs() + self.has_arcs = self.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() @@ -167,7 +170,7 @@ class HtmlReporter(Reporter): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) - self.coverage.data.add_to_hash(fr.filename, m) + add_data_to_hash(self.data, fr.filename, m) return m.hexdigest() def html_file(self, fr, analysis): diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js index f6f5de20..2b32c391 100644 --- a/coverage/htmlfiles/coverage_html.js +++ b/coverage/htmlfiles/coverage_html.js @@ -1,5 +1,5 @@ // Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -// For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt // Coverage.py HTML report browser code. /*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ @@ -169,22 +169,13 @@ coverage.wire_up_filter = function () { // Loaded on index.html coverage.index_ready = function ($) { - // Look for a cookie containing previous sort settings: + // Look for a localStorage item containing previous sort settings: var sort_list = []; - var cookie_name = "COVERAGE_INDEX_SORT"; - var i; + var storage_name = "COVERAGE_INDEX_SORT"; + var stored_list = localStorage.getItem(storage_name); - // 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; - } - } + if (stored_list) { + sort_list = JSON.parse('[[' + stored_list + ']]'); } // Create a new widget which exists only to save and restore @@ -231,7 +222,7 @@ coverage.index_ready = function ($) { // Watch for page unload events so we can save the final sort settings: $(window).unload(function () { - document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/"; + localStorage.setItem(storage_name, sort_list.toString()) }); }; @@ -555,11 +546,16 @@ coverage.resize_scroll_markers = function () { var previous_line = -99, last_mark, - last_top; + last_top, + offsets = {}; + // Calculate line offsets outside loop to prevent relayouts + c.missed_lines.each(function() { + offsets[this.id] = $(this).offset().top; + }); c.missed_lines.each(function () { - var line_top = Math.round($(this).offset().top * marker_scale), - id_name = $(this).attr('id'), + var id_name = $(this).attr('id'), + line_top = Math.round(offsets[id_name] * marker_scale), line_number = parseInt(id_name.substring(1, id_name.length)); if (line_number === previous_line + 1) { diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html index 1e3999f9..4129bc31 100644 --- a/coverage/htmlfiles/index.html +++ b/coverage/htmlfiles/index.html @@ -1,5 +1,5 @@ {# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #} -{# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt #} +{# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #} <!DOCTYPE html> <html> diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html index 8542a467..245ecf41 100644 --- a/coverage/htmlfiles/pyfile.html +++ b/coverage/htmlfiles/pyfile.html @@ -1,5 +1,5 @@ {# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #} -{# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt #} +{# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #} <!DOCTYPE html> <html> diff --git a/coverage/htmlfiles/style.css b/coverage/htmlfiles/style.css index 86b82091..14592865 100644 --- a/coverage/htmlfiles/style.css +++ b/coverage/htmlfiles/style.css @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ /* CSS styles for coverage.py. */ @@ -365,6 +365,7 @@ td.text { height: 100%; background: white; border-left: 1px solid #eee; + will-change: transform; /* for faster scrolling of fixed element in Chrome */ } #scroll_marker .marker { diff --git a/coverage/inorout.py b/coverage/inorout.py new file mode 100644 index 00000000..15e496af --- /dev/null +++ b/coverage/inorout.py @@ -0,0 +1,461 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Determining whether files are being measured/reported or not.""" + +# For finding the stdlib +import atexit +import inspect +import itertools +import os +import platform +import re +import sys +import traceback + +from coverage import env +from coverage.disposition import FileDisposition, disposition_init +from coverage.files import TreeMatcher, FnmatchMatcher, ModuleMatcher +from coverage.files import prep_patterns, find_python_files, canonical_filename +from coverage.misc import CoverageException +from coverage.python import source_for_file, source_for_morf + + +# Pypy has some unusual stuff in the "stdlib". Consider those locations +# when deciding where the stdlib is. These modules are not used for anything, +# they are modules importable from the pypy lib directories, so that we can +# find those directories. +_structseq = _pypy_irc_topic = None +if env.PYPY: + try: + import _structseq + except ImportError: + pass + + try: + import _pypy_irc_topic + except ImportError: + pass + + +def canonical_path(morf, directory=False): + """Return the canonical path of the module or file `morf`. + + If the module is a package, then return its directory. If it is a + module, then return its file, unless `directory` is True, in which + case return its enclosing directory. + + """ + morf_path = canonical_filename(source_for_morf(morf)) + if morf_path.endswith("__init__.py") or directory: + morf_path = os.path.split(morf_path)[0] + return morf_path + + +def name_for_module(filename, frame): + """Get the name of the module for a filename and frame. + + For configurability's sake, we allow __main__ modules to be matched by + their importable name. + + If loaded via runpy (aka -m), we can usually recover the "original" + full dotted module name, otherwise, we resort to interpreting the + file name to get the module's name. In the case that the module name + can't be determined, None is returned. + + """ + module_globals = frame.f_globals if frame is not None else {} + if module_globals is None: # pragma: only ironpython + # IronPython doesn't provide globals: https://github.com/IronLanguages/main/issues/1296 + module_globals = {} + + dunder_name = module_globals.get('__name__', None) + + if isinstance(dunder_name, str) and dunder_name != '__main__': + # This is the usual case: an imported module. + return dunder_name + + loader = module_globals.get('__loader__', None) + for attrname in ('fullname', 'name'): # attribute renamed in py3.2 + if hasattr(loader, attrname): + fullname = getattr(loader, attrname) + else: + continue + + if isinstance(fullname, str) and fullname != '__main__': + # Module loaded via: runpy -m + return fullname + + # Script as first argument to Python command line. + inspectedname = inspect.getmodulename(filename) + if inspectedname is not None: + return inspectedname + else: + return dunder_name + + +def module_is_namespace(mod): + """Is the module object `mod` a PEP420 namespace module?""" + return hasattr(mod, '__path__') and getattr(mod, '__file__', None) is None + + +def module_has_file(mod): + """Does the module object `mod` have an existing __file__ ?""" + mod__file__ = getattr(mod, '__file__', None) + if mod__file__ is None: + return False + return os.path.exists(mod__file__) + + +class InOrOut(object): + """Machinery for determining what files to measure.""" + + def __init__(self, warn): + self.warn = warn + + # The matchers for should_trace. + self.source_match = None + self.source_pkgs_match = None + self.pylib_paths = self.cover_paths = None + self.pylib_match = self.cover_match = None + self.include_match = self.omit_match = None + self.plugins = [] + self.disp_class = FileDisposition + + # The source argument can be directories or package names. + self.source = [] + self.source_pkgs = [] + self.source_pkgs_unmatched = [] + self.omit = self.include = None + + def configure(self, config): + """Apply the configuration to get ready for decision-time.""" + for src in config.source or []: + if os.path.isdir(src): + self.source.append(canonical_filename(src)) + else: + self.source_pkgs.append(src) + self.source_pkgs_unmatched = self.source_pkgs[:] + + self.omit = prep_patterns(config.run_omit) + self.include = prep_patterns(config.run_include) + + # The directories for files considered "installed with the interpreter". + self.pylib_paths = set() + if not 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, inspect, os, platform, _pypy_irc_topic, re, _structseq, traceback): + if m is not None and hasattr(m, "__file__"): + self.pylib_paths.add(canonical_path(m, directory=True)) + + if _structseq and not hasattr(_structseq, '__file__'): + # PyPy 2.4 has no __file__ in the builtin modules, but the code + # objects still have the file names. So dig into one to find + # the path to exclude. + structseq_new = _structseq.structseq_new + try: + structseq_file = structseq_new.func_code.co_filename + except AttributeError: + structseq_file = structseq_new.__code__.co_filename + self.pylib_paths.add(canonical_path(structseq_file)) + + # To avoid tracing the coverage.py code itself, we skip anything + # located where we are. + self.cover_paths = [canonical_path(__file__, directory=True)] + if env.TESTING: + # Don't include our own test code. + self.cover_paths.append(os.path.join(self.cover_paths[0], "tests")) + + # When testing, we use PyContracts, which should be considered + # part of coverage.py, and it uses six. Exclude those directories + # just as we exclude ourselves. + import contracts + import six + for mod in [contracts, six]: + self.cover_paths.append(canonical_path(mod)) + + # Create the matchers we need for should_trace + if self.source or self.source_pkgs: + self.source_match = TreeMatcher(self.source) + self.source_pkgs_match = ModuleMatcher(self.source_pkgs) + else: + if self.cover_paths: + self.cover_match = TreeMatcher(self.cover_paths) + if self.pylib_paths: + self.pylib_match = TreeMatcher(self.pylib_paths) + if self.include: + self.include_match = FnmatchMatcher(self.include) + if self.omit: + self.omit_match = FnmatchMatcher(self.omit) + + def should_trace(self, filename, frame=None): + """Decide whether to trace execution in `filename`, with a reason. + + This function is called from the trace function. As each new file name + is encountered, this function determines whether it is traced or not. + + Returns a FileDisposition object. + + """ + original_filename = filename + disp = disposition_init(self.disp_class, filename) + + def nope(disp, reason): + """Simple helper to make it easy to return NO.""" + disp.trace = False + disp.reason = reason + return disp + + if frame is not None: + # Compiled Python files have two file names: frame.f_code.co_filename is + # the file name 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 and frame.f_globals.get('__file__') + if dunder_file: + filename = source_for_file(dunder_file) + if original_filename and not original_filename.startswith('<'): + orig = os.path.basename(original_filename) + if orig != os.path.basename(filename): + # Files shouldn't be renamed when moved. This happens when + # exec'ing code. If it seems like something is wrong with + # the frame's file name, then just use the original. + filename = original_filename + + if not filename: + # Empty string is pretty useless. + return nope(disp, "empty string isn't a file name") + + if filename.startswith('memory:'): + return nope(disp, "memory isn't traceable") + + if filename.startswith('<'): + # Lots of non-file execution is represented with artificial + # file names 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 nope(disp, "not a real file name") + + # pyexpat does a dumb thing, calling the trace function explicitly from + # C code with a C file name. + if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename): + return nope(disp, "pyexpat lies about itself") + + # Jython reports the .class file to the tracer, use the source file. + if filename.endswith("$py.class"): + filename = filename[:-9] + ".py" + + canonical = canonical_filename(filename) + disp.canonical_filename = canonical + + # Try the plugins, see if they have an opinion about the file. + plugin = None + for plugin in self.plugins.file_tracers: + if not plugin._coverage_enabled: + continue + + try: + file_tracer = plugin.file_tracer(canonical) + if file_tracer is not None: + file_tracer._coverage_plugin = plugin + disp.trace = True + disp.file_tracer = file_tracer + if file_tracer.has_dynamic_source_filename(): + disp.has_dynamic_filename = True + else: + disp.source_filename = canonical_filename( + file_tracer.source_filename() + ) + break + except Exception: + self.warn( + "Disabling plug-in %r due to an exception:" % (plugin._coverage_plugin_name) + ) + traceback.print_exc() + plugin._coverage_enabled = False + continue + else: + # No plugin wanted it: it's Python. + disp.trace = True + disp.source_filename = canonical + + if not disp.has_dynamic_filename: + if not disp.source_filename: + raise CoverageException( + "Plugin %r didn't set source_filename for %r" % + (plugin, disp.original_filename) + ) + reason = self.check_include_omit_etc(disp.source_filename, frame) + if reason: + nope(disp, reason) + + return disp + + def check_include_omit_etc(self, filename, frame): + """Check a file name against the include, omit, etc, rules. + + Returns a string or None. String means, don't trace, and is the reason + why. None means no reason found to not trace. + + """ + modulename = name_for_module(filename, frame) + + # 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 self.source_pkgs_match.match(modulename): + if modulename in self.source_pkgs_unmatched: + self.source_pkgs_unmatched.remove(modulename) + elif not self.source_match.match(filename): + return "falls outside the --source trees" + elif self.include_match: + if not self.include_match.match(filename): + return "falls outside the --include trees" + else: + # If we aren't supposed to trace installed code, then check if this + # is near the Python standard library and skip it if so. + if self.pylib_match and self.pylib_match.match(filename): + return "is in the stdlib" + + # We exclude the coverage.py code itself, since a little of it + # will be measured otherwise. + if self.cover_match and self.cover_match.match(filename): + return "is part of coverage.py" + + # Check the file against the omit pattern. + if self.omit_match and self.omit_match.match(filename): + return "is inside an --omit pattern" + + # No reason found to skip this file. + return None + + def warn_conflicting_settings(self): + """Warn if there are settings that conflict.""" + if self.include: + if self.source or self.source_pkgs: + self.warn("--include is ignored because --source is set", slug="include-ignored") + + def warn_already_imported_files(self): + """Warn if files have already been imported that we will be measuring.""" + if self.include or self.source or self.source_pkgs: + warned = set() + for mod in list(sys.modules.values()): + filename = getattr(mod, "__file__", None) + if filename is None: + continue + if filename in warned: + continue + + disp = self.should_trace(filename) + if disp.trace: + msg = "Already imported a file that will be measured: {0}".format(filename) + self.warn(msg, slug="already-imported") + warned.add(filename) + + def warn_unimported_source(self): + """Warn about source packages that were of interest, but never traced.""" + for pkg in self.source_pkgs_unmatched: + self._warn_about_unmeasured_code(pkg) + + def _warn_about_unmeasured_code(self, pkg): + """Warn about a package or module that we never traced. + + `pkg` is a string, the name of the package or module. + + """ + mod = sys.modules.get(pkg) + if mod is None: + self.warn("Module %s was never imported." % pkg, slug="module-not-imported") + return + + if module_is_namespace(mod): + # A namespace package. It's OK for this not to have been traced, + # since there is no code directly in it. + return + + if not module_has_file(mod): + self.warn("Module %s has no Python source." % pkg, slug="module-not-python") + return + + # The module was in sys.modules, and seems like a module with code, but + # we never measured it. I guess that means it was imported before + # coverage even started. + self.warn( + "Module %s was previously imported, but not measured" % pkg, + slug="module-not-measured", + ) + + def find_unexecuted_files(self): + """Find files in the areas of interest that weren't traced. + + Yields pairs: file path, and responsible plug-in name. + """ + for pkg in self.source_pkgs: + if (not pkg in sys.modules or + not module_has_file(sys.modules[pkg])): + continue + pkg_file = source_for_file(sys.modules[pkg].__file__) + for ret in self._find_unexecuted_files(canonical_path(pkg_file)): + yield ret + + for src in self.source: + for ret in self._find_unexecuted_files(src): + yield ret + + def _find_plugin_files(self, src_dir): + """Get executable files from the plugins.""" + for plugin in self.plugins.file_tracers: + for x_file in plugin.find_executable_files(src_dir): + yield x_file, plugin._coverage_plugin_name + + def _find_unexecuted_files(self, src_dir): + """Find unexecuted files in `src_dir`. + + Search for files in `src_dir` that are probably importable, + and add them as unexecuted files in `self.data`. + + """ + py_files = ((py_file, None) for py_file in find_python_files(src_dir)) + plugin_files = self._find_plugin_files(src_dir) + + for file_path, plugin_name in itertools.chain(py_files, plugin_files): + file_path = canonical_filename(file_path) + if self.omit_match and self.omit_match.match(file_path): + # Turns out this file was omitted, so don't pull it back + # in as unexecuted. + continue + yield file_path, plugin_name + + def sys_info(self): + """Our information for Coverage.sys_info. + + Returns a list of (key, value) pairs. + """ + info = [ + ('cover_paths', self.cover_paths), + ('pylib_paths', self.pylib_paths), + ] + + matcher_names = [ + 'source_match', 'source_pkgs_match', + 'include_match', 'omit_match', + 'cover_match', 'pylib_match', + ] + + for matcher_name in matcher_names: + matcher = getattr(self, matcher_name) + if matcher: + matcher_info = matcher.info() + else: + matcher_info = '-none-' + info.append((matcher_name, matcher_info)) + + return info diff --git a/coverage/misc.py b/coverage/misc.py index 28aa3b06..a923829d 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Miscellaneous stuff for coverage.py.""" @@ -8,6 +8,7 @@ import hashlib import inspect import locale import os +import re import sys import types @@ -69,11 +70,11 @@ if env.TESTING: """Ensure that only one of the argnames is non-None.""" def _decorator(func): argnameset = set(name.strip() for name in argnames.split(",")) - def _wrapped(*args, **kwargs): + def _wrapper(*args, **kwargs): vals = [kwargs.get(name) for name in argnameset] assert sum(val is not None for val in vals) == 1 return func(*args, **kwargs) - return _wrapped + return _wrapper return _decorator else: # pragma: not testing # We aren't using real PyContracts, so just define our decorators as @@ -148,13 +149,12 @@ def expensive(fn): if env.TESTING: attr = "_once_" + fn.__name__ - def _wrapped(self): - """Inner function that checks the cache.""" + def _wrapper(self): if hasattr(self, attr): raise AssertionError("Shouldn't have called %s more than once" % fn.__name__) setattr(self, attr, True) return fn(self) - return _wrapped + return _wrapper else: return fn # pragma: not testing @@ -226,6 +226,7 @@ class Hasher(object): continue self.update(k) self.update(a) + self.md5.update(b'.') def hexdigest(self): """Retrieve the hex digest of the hash.""" @@ -249,14 +250,54 @@ def _needs_to_implement(that, func_name): ) -class SimpleRepr(object): - """A mixin implementing a simple __repr__.""" - def __repr__(self): - return "<{klass} @{id:x} {attrs}>".format( - klass=self.__class__.__name__, - id=id(self) & 0xFFFFFF, - attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()), - ) +def substitute_variables(text, variables=os.environ): + """Substitute ``${VAR}`` variables in `text` with their values. + + Variables in the text can take a number of shell-inspired forms:: + + $VAR + ${VAR} + ${VAR?} strict: an error if VAR isn't defined. + ${VAR-missing} defaulted: "missing" if VAR isn't defined. + + A dollar can be inserted with ``$$``. + + `variables` is a dictionary of variable values, defaulting to the + environment variables. + + Returns the resulting text with values substituted. + + """ + def dollar_replace(m): + """Called for each $replacement.""" + # Only one of the groups will have matched, just get its text. + word = ''.join(m.group(name) or '' for name in ['v1', 'v2', 'char']) + if word == "$": + return "$" + else: + strict = bool(m.group('strict')) + if strict: + if word not in variables: + msg = "Variable {} is undefined: {}".format(word, text) + raise CoverageException(msg) + return variables.get(word, m.group('defval') or '') + + dollar_pattern = r"""(?x) # Use extended regex syntax + \$(?: # A dollar sign, then + (?P<v1>\w+) | # a plain word, + (?P<char>\$) | # or a dollar sign. + { # or a {-wrapped word, + (?P<v2>\w+) + (?: + (?P<strict>\?) # with a strict marker + | + -(?P<defval>[^}]*) # or a default value + )? + } + ) + """ + text = re.sub(dollar_pattern, dollar_replace, text) + return text class BaseCoverageException(Exception): diff --git a/coverage/multiproc.py b/coverage/multiproc.py index fe837318..62f6beb7 100644 --- a/coverage/multiproc.py +++ b/coverage/multiproc.py @@ -1,24 +1,21 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Monkey-patching to add multiprocessing support for coverage.py""" import multiprocessing import multiprocessing.process import os -import sys +from coverage import env from coverage.misc import contract # An attribute that will be set on the module to indicate that it has been # monkey-patched. PATCHED_MARKER = "_coverage$patched" -# The environment variable that specifies the rcfile for subprocesses. -COVERAGE_RCFILE_ENV = "_COVERAGE_RCFILE" - -if sys.version_info >= (3, 4): +if env.PYVERSION >= (3, 4): OriginalProcess = multiprocessing.process.BaseProcess else: OriginalProcess = multiprocessing.Process @@ -31,10 +28,10 @@ class ProcessWithCoverage(OriginalProcess): def _bootstrap(self): """Wrapper around _bootstrap to start coverage.""" from coverage import Coverage # avoid circular import - rcfile = os.environ[COVERAGE_RCFILE_ENV] - cov = Coverage(data_suffix=True, config_file=rcfile) + cov = Coverage(data_suffix=True) + cov._warn_preimported_source = False cov.start() - debug = cov.debug + debug = cov._debug try: if debug.should("multiproc"): debug.write("Calling multiprocessing bootstrap") @@ -73,14 +70,14 @@ def patch_multiprocessing(rcfile): if hasattr(multiprocessing, PATCHED_MARKER): return - if sys.version_info >= (3, 4): + if env.PYVERSION >= (3, 4): OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap else: multiprocessing.Process = ProcessWithCoverage # Set the value in ProcessWithCoverage that will be pickled into the child # process. - os.environ[COVERAGE_RCFILE_ENV] = rcfile + os.environ["COVERAGE_RCFILE"] = rcfile # When spawning processes rather than forking them, we have no state in the # new process. We sneak in there with a Stowaway: we stuff one of our own diff --git a/coverage/parser.py b/coverage/parser.py index 6e6cccd5..c9eb793f 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Code parsing for coverage.py.""" diff --git a/coverage/phystokens.py b/coverage/phystokens.py index a2b23cfc..ccfe63b3 100644 --- a/coverage/phystokens.py +++ b/coverage/phystokens.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Better tokenizing for coverage.py.""" diff --git a/coverage/pickle2json.py b/coverage/pickle2json.py deleted file mode 100644 index 95b42ef3..00000000 --- a/coverage/pickle2json.py +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -"""Convert pickle to JSON for coverage.py.""" - -from coverage.backward import pickle -from coverage.data import CoverageData - - -def pickle_read_raw_data(cls_unused, file_obj): - """Replacement for CoverageData._read_raw_data.""" - return pickle.load(file_obj) - - -def pickle2json(infile, outfile): - """Convert a coverage.py 3.x pickle data file to a 4.x JSON data file.""" - try: - old_read_raw_data = CoverageData._read_raw_data - CoverageData._read_raw_data = pickle_read_raw_data - - covdata = CoverageData() - - with open(infile, 'rb') as inf: - covdata.read_fileobj(inf) - - covdata.write_file(outfile) - finally: - CoverageData._read_raw_data = old_read_raw_data - - -if __name__ == "__main__": - from optparse import OptionParser - - parser = OptionParser(usage="usage: %s [options]" % __file__) - parser.description = "Convert .coverage files from pickle to JSON format" - parser.add_option( - "-i", "--input-file", action="store", default=".coverage", - help="Name of input file. Default .coverage", - ) - parser.add_option( - "-o", "--output-file", action="store", default=".coverage", - help="Name of output file. Default .coverage", - ) - - (options, args) = parser.parse_args() - - pickle2json(options.input_file, options.output_file) diff --git a/coverage/plugin.py b/coverage/plugin.py index db7ca0a7..f65d419c 100644 --- a/coverage/plugin.py +++ b/coverage/plugin.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """ .. versionadded:: 4.0 @@ -134,7 +134,8 @@ class CoveragePlugin(object): This will only be invoked if `filename` returns non-None from :meth:`file_tracer`. It's an error to return None from this method. - Returns a :class:`FileReporter` object to use to report on `filename`. + Returns a :class:`FileReporter` object to use to report on `filename`, + or the string `"python"` to have coverage.py treat the file as Python. """ _needs_to_implement(self, "file_reporter") diff --git a/coverage/plugin_support.py b/coverage/plugin_support.py index c737a42c..0727a3b0 100644 --- a/coverage/plugin_support.py +++ b/coverage/plugin_support.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Support for plugins.""" diff --git a/coverage/python.py b/coverage/python.py index 372347f5..31db1a27 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Python source expertise for coverage.py""" @@ -97,7 +97,7 @@ def get_zip_bytes(filename): def source_for_file(filename): - """Return the source file for `filename`. + """Return the source filename for `filename`. Given a file name being traced, return the best guess as to the source file to attribute it to. @@ -129,22 +129,28 @@ def source_for_file(filename): return filename +def source_for_morf(morf): + """Get the source filename for the module-or-file `morf`.""" + if hasattr(morf, '__file__') and morf.__file__: + filename = morf.__file__ + elif isinstance(morf, types.ModuleType): + # A module should have had .__file__, otherwise we can't use it. + # This could be a PEP-420 namespace package. + raise CoverageException("Module {0} has no file".format(morf)) + else: + filename = morf + + filename = source_for_file(files.unicode_filename(filename)) + return filename + + class PythonFileReporter(FileReporter): """Report support for a Python file.""" def __init__(self, morf, coverage=None): self.coverage = coverage - if hasattr(morf, '__file__'): - filename = morf.__file__ - elif isinstance(morf, types.ModuleType): - # A module should have had .__file__, otherwise we can't use it. - # This could be a PEP-420 namespace package. - raise CoverageException("Module {0} has no file".format(morf)) - else: - filename = morf - - filename = source_for_file(files.unicode_filename(filename)) + filename = source_for_morf(morf) super(PythonFileReporter, self).__init__(files.canonical_filename(filename)) diff --git a/coverage/pytracer.py b/coverage/pytracer.py index 7e70bab6..d0549f72 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Raw data collector for coverage.py.""" diff --git a/coverage/report.py b/coverage/report.py index b4608633..e4378f6d 100644 --- a/coverage/report.py +++ b/coverage/report.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Reporter foundation for coverage.py.""" diff --git a/coverage/results.py b/coverage/results.py index 5f84a689..cab8796e 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -1,12 +1,13 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Results of coverage measurement.""" import collections from coverage.backward import iitems -from coverage.misc import contract, format_lines, SimpleRepr +from coverage.debug import SimpleReprMixin +from coverage.misc import contract, format_lines class Analysis(object): @@ -157,7 +158,7 @@ class Analysis(object): return stats -class Numbers(SimpleRepr): +class Numbers(SimpleReprMixin): """The numerical results of measuring coverage. This holds the basic statistics from `Analysis`, and is used to roll diff --git a/coverage/sqldata.py b/coverage/sqldata.py new file mode 100644 index 00000000..e9ccbede --- /dev/null +++ b/coverage/sqldata.py @@ -0,0 +1,535 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Sqlite coverage data.""" + +# TODO: get sys_info for data class, so we can see sqlite version etc +# TODO: get rid of skip_unless_data_storage_is +# TODO: get rid of "JSON message" and "SQL message" in the tests +# TODO: factor out dataop debugging to a wrapper class? +# TODO: make sure all dataop debugging is in place somehow +# TODO: should writes be batched? +# TODO: run_info + +import glob +import itertools +import os +import sqlite3 + +from coverage.backward import iitems +from coverage.data import filename_suffix +from coverage.debug import SimpleReprMixin +from coverage.files import PathAliases +from coverage.misc import CoverageException, file_be_gone + + +# Schema versions: +# 1: Released in 5.0a2 +# 2: Added contexts + +SCHEMA_VERSION = 2 + +SCHEMA = """ +create table coverage_schema ( + version integer +); + +create table meta ( + has_lines boolean, + has_arcs boolean +); + +create table file ( + id integer primary key, + path text, + unique(path) +); + +create table context ( + id integer primary key, + context text, + unique(context) +); + +create table line ( + file_id integer, + context_id integer, + lineno integer, + unique(file_id, context_id, lineno) +); + +create table arc ( + file_id integer, + context_id integer, + fromno integer, + tono integer, + unique(file_id, context_id, fromno, tono) +); + +create table tracer ( + file_id integer primary key, + tracer text +); +""" + + +class CoverageSqliteData(SimpleReprMixin): + def __init__(self, basename=None, suffix=None, warn=None, debug=None): + self._basename = os.path.abspath(basename or ".coverage") + self._suffix = suffix + self._warn = warn + self._debug = debug + + self._choose_filename() + self._file_map = {} + self._db = None + self._pid = os.getpid() + + # Are we in sync with the data file? + self._have_used = False + + self._has_lines = False + self._has_arcs = False + + self._current_context_id = None + + def _choose_filename(self): + self.filename = self._basename + suffix = filename_suffix(self._suffix) + if suffix: + self.filename += "." + suffix + + def _reset(self): + if self._db is not None: + self._db.close() + self._db = None + self._file_map = {} + self._have_used = False + self._current_context_id = None + + def _create_db(self): + if self._debug and self._debug.should('dataio'): + self._debug.write("Creating data file {!r}".format(self.filename)) + self._db = Sqlite(self.filename, self._debug) + with self._db: + for stmt in SCHEMA.split(';'): + stmt = " ".join(stmt.strip().split()) + if stmt: + self._db.execute(stmt) + self._db.execute("insert into coverage_schema (version) values (?)", (SCHEMA_VERSION,)) + self._db.execute( + "insert into meta (has_lines, has_arcs) values (?, ?)", + (self._has_lines, self._has_arcs) + ) + + def _open_db(self): + if self._debug and self._debug.should('dataio'): + self._debug.write("Opening data file {!r}".format(self.filename)) + self._db = Sqlite(self.filename, self._debug) + with self._db: + try: + schema_version, = self._db.execute("select version from coverage_schema").fetchone() + except Exception as exc: + raise CoverageException( + "Data file {!r} doesn't seem to be a coverage data file: {}".format( + self.filename, exc + ) + ) + else: + if schema_version != SCHEMA_VERSION: + raise CoverageException( + "Couldn't use data file {!r}: wrong schema: {} instead of {}".format( + self.filename, schema_version, SCHEMA_VERSION + ) + ) + + for row in self._db.execute("select has_lines, has_arcs from meta"): + self._has_lines, self._has_arcs = row + + for path, id in self._db.execute("select path, id from file"): + self._file_map[path] = id + + def _connect(self): + if self._db is None: + if os.path.exists(self.filename): + self._open_db() + else: + self._create_db() + return self._db + + def __nonzero__(self): + if self._db is None and not os.path.exists(self.filename): + return False + try: + with self._connect() as con: + rows = con.execute("select * from file limit 1") + return bool(list(rows)) + except CoverageException: + return False + + __bool__ = __nonzero__ + + def dump(self): # pragma: debugging + """Write a dump of the database.""" + if self._debug: + with self._connect() as con: + self._debug.write(con.dump()) + + def _file_id(self, filename, add=False): + """Get the file id for `filename`. + + If filename is not in the database yet, add if it `add` is True. + If `add` is not True, return None. + """ + if filename not in self._file_map: + if add: + with self._connect() as con: + cur = con.execute("insert into file (path) values (?)", (filename,)) + self._file_map[filename] = cur.lastrowid + return self._file_map.get(filename) + + def _context_id(self, context): + """Get the id for a context.""" + assert context is not None + self._start_using() + with self._connect() as con: + row = con.execute("select id from context where context = ?", (context,)).fetchone() + if row is not None: + return row[0] + else: + return None + + def set_context(self, context): + """Set the current context for future `add_lines` etc.""" + if self._debug and self._debug.should('dataop'): + self._debug.write("Setting context: %r" % (context,)) + self._start_using() + context = context or "" + with self._connect() as con: + row = con.execute("select id from context where context = ?", (context,)).fetchone() + if row is not None: + self._current_context_id = row[0] + else: + cur = con.execute("insert into context (context) values (?)", (context,)) + self._current_context_id = cur.lastrowid + + def add_lines(self, line_data): + """Add measured line data. + + `line_data` is a dictionary mapping file names to dictionaries:: + + { filename: { lineno: None, ... }, ...} + + """ + if self._debug and self._debug.should('dataop'): + self._debug.write("Adding lines: %d files, %d lines total" % ( + len(line_data), sum(len(lines) for lines in line_data.values()) + )) + self._start_using() + self._choose_lines_or_arcs(lines=True) + if self._current_context_id is None: + self.set_context("") + with self._connect() as con: + for filename, linenos in iitems(line_data): + file_id = self._file_id(filename, add=True) + data = [(file_id, self._current_context_id, lineno) for lineno in linenos] + con.executemany( + "insert or ignore into line (file_id, context_id, lineno) values (?, ?, ?)", + data, + ) + + def add_arcs(self, arc_data): + """Add measured arc data. + + `arc_data` is a dictionary mapping file names to dictionaries:: + + { filename: { (l1,l2): None, ... }, ...} + + """ + if self._debug and self._debug.should('dataop'): + self._debug.write("Adding arcs: %d files, %d arcs total" % ( + len(arc_data), sum(len(arcs) for arcs in arc_data.values()) + )) + self._start_using() + self._choose_lines_or_arcs(arcs=True) + if self._current_context_id is None: + self.set_context("") + with self._connect() as con: + for filename, arcs in iitems(arc_data): + file_id = self._file_id(filename, add=True) + data = [(file_id, self._current_context_id, fromno, tono) for fromno, tono in arcs] + con.executemany( + "insert or ignore into arc (file_id, context_id, fromno, tono) values (?, ?, ?, ?)", + data, + ) + + def _choose_lines_or_arcs(self, lines=False, arcs=False): + if lines and self._has_arcs: + raise CoverageException("Can't add lines to existing arc data") + if arcs and self._has_lines: + raise CoverageException("Can't add arcs to existing line data") + if not self._has_arcs and not self._has_lines: + self._has_lines = lines + self._has_arcs = arcs + with self._connect() as con: + con.execute("update meta set has_lines = ?, has_arcs = ?", (lines, arcs)) + + def add_file_tracers(self, file_tracers): + """Add per-file plugin information. + + `file_tracers` is { filename: plugin_name, ... } + + """ + self._start_using() + with self._connect() as con: + for filename, plugin_name in iitems(file_tracers): + file_id = self._file_id(filename) + if file_id is None: + raise CoverageException( + "Can't add file tracer data for unmeasured file '%s'" % (filename,) + ) + + existing_plugin = self.file_tracer(filename) + if existing_plugin: + if existing_plugin != plugin_name: + raise CoverageException( + "Conflicting file tracer name for '%s': %r vs %r" % ( + filename, existing_plugin, plugin_name, + ) + ) + elif plugin_name: + con.execute( + "insert into tracer (file_id, tracer) values (?, ?)", + (file_id, plugin_name) + ) + + def touch_file(self, filename, plugin_name=""): + """Ensure that `filename` appears in the data, empty if needed. + + `plugin_name` is the name of the plugin resposible for this file. It is used + to associate the right filereporter, etc. + """ + self._start_using() + if self._debug and self._debug.should('dataop'): + self._debug.write("Touching %r" % (filename,)) + if not self._has_arcs and not self._has_lines: + raise CoverageException("Can't touch files in an empty CoverageSqliteData") + + self._file_id(filename, add=True) + if plugin_name: + # Set the tracer for this file + self.add_file_tracers({filename: plugin_name}) + + def update(self, other_data, aliases=None): + if self._has_lines and other_data._has_arcs: + raise CoverageException("Can't combine arc data with line data") + if self._has_arcs and other_data._has_lines: + raise CoverageException("Can't combine line data with arc data") + + aliases = aliases or PathAliases() + + # See what we had already measured, for accurate conflict reporting. + this_measured = self.measured_files() + + # lines + if other_data._has_lines: + for context in other_data.measured_contexts(): + self.set_context(context) + for filename in other_data.measured_files(): + lines = set(other_data.lines(filename, context=context)) + filename = aliases.map(filename) + lines.update(self.lines(filename, context=context) or ()) + self.add_lines({filename: lines}) + + # arcs + if other_data._has_arcs: + for context in other_data.measured_contexts(): + self.set_context(context) + for filename in other_data.measured_files(): + arcs = set(other_data.arcs(filename, context=context)) + filename = aliases.map(filename) + arcs.update(self.arcs(filename, context=context) or ()) + self.add_arcs({filename: arcs}) + + # file_tracers + for filename in other_data.measured_files(): + other_plugin = other_data.file_tracer(filename) + filename = aliases.map(filename) + if filename in this_measured: + this_plugin = self.file_tracer(filename) + else: + this_plugin = None + if this_plugin is None: + self.add_file_tracers({filename: other_plugin}) + elif this_plugin != other_plugin: + raise CoverageException( + "Conflicting file tracer name for '%s': %r vs %r" % ( + filename, this_plugin, other_plugin, + ) + ) + + def erase(self, parallel=False): + """Erase the data in this object. + + If `parallel` is true, then also deletes data files created from the + basename by parallel-mode. + + """ + self._reset() + if self._debug and self._debug.should('dataio'): + self._debug.write("Erasing data file {!r}".format(self.filename)) + file_be_gone(self.filename) + if parallel: + data_dir, local = os.path.split(self.filename) + localdot = local + '.*' + pattern = os.path.join(os.path.abspath(data_dir), localdot) + for filename in glob.glob(pattern): + if self._debug and self._debug.should('dataio'): + self._debug.write("Erasing parallel data file {!r}".format(filename)) + file_be_gone(filename) + + def read(self): + with self._connect(): # TODO: doesn't look right + self._have_used = True + + def write(self): + """Write the collected coverage data to a file.""" + pass + + def _start_using(self): + if self._pid != os.getpid(): + # Looks like we forked! Have to start a new data file. + self._reset() + self._choose_filename() + self._pid = os.getpid() + if not self._have_used: + self.erase() + self._have_used = True + + def has_arcs(self): + return bool(self._has_arcs) + + def measured_files(self): + """A set of all files that had been measured.""" + return set(self._file_map) + + def measured_contexts(self): + """A set of all contexts that have been measured.""" + self._start_using() + with self._connect() as con: + contexts = set(row[0] for row in con.execute("select distinct(context) from context")) + return contexts + + def file_tracer(self, filename): + """Get the plugin name of the file tracer for a file. + + Returns the name of the plugin that handles this file. If the file was + measured, but didn't use a plugin, then "" is returned. If the file + was not measured, then None is returned. + + """ + self._start_using() + with self._connect() as con: + file_id = self._file_id(filename) + if file_id is None: + return None + row = con.execute("select tracer from tracer where file_id = ?", (file_id,)).fetchone() + if row is not None: + return row[0] or "" + return "" # File was measured, but no tracer associated. + + def lines(self, filename, context=None): + self._start_using() + if self.has_arcs(): + arcs = self.arcs(filename, context=context) + if arcs is not None: + all_lines = itertools.chain.from_iterable(arcs) + return list(set(l for l in all_lines if l > 0)) + + with self._connect() as con: + file_id = self._file_id(filename) + if file_id is None: + return None + else: + query = "select lineno from line where file_id = ?" + data = [file_id] + if context is not None: + query += " and context_id = ?" + data += [self._context_id(context)] + linenos = con.execute(query, data) + return [lineno for lineno, in linenos] + + def arcs(self, filename, context=None): + self._start_using() + with self._connect() as con: + file_id = self._file_id(filename) + if file_id is None: + return None + else: + query = "select fromno, tono from arc where file_id = ?" + data = [file_id] + if context is not None: + query += " and context_id = ?" + data += [self._context_id(context)] + arcs = con.execute(query, data) + return list(arcs) + + def run_infos(self): + return [] # TODO + + +class Sqlite(SimpleReprMixin): + def __init__(self, filename, debug): + self.debug = debug if (debug and debug.should('sql')) else None + if self.debug: + self.debug.write("Connecting to {!r}".format(filename)) + self.filename = filename + self.nest = 0 + + def connect(self): + # SQLite on Windows on py2 won't open a file if the filename argument + # has non-ascii characters in it. Opening a relative file name avoids + # a problem if the current directory has non-ascii. + filename = os.path.relpath(self.filename) + self.con = sqlite3.connect(filename) + + # This pragma makes writing faster. It disables rollbacks, but we never need them. + # PyPy needs the .close() calls here, or sqlite gets twisted up: + # https://bitbucket.org/pypy/pypy/issues/2872/default-isolation-mode-is-different-on + self.execute("pragma journal_mode=off").close() + # This pragma makes writing faster. + self.execute("pragma synchronous=off").close() + + def close(self): + self.con.close() + + def __enter__(self): + if self.nest == 0: + self.connect() + self.con.__enter__() + self.nest += 1 + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.nest -= 1 + if self.nest == 0: + self.con.__exit__(exc_type, exc_value, traceback) + self.close() + + def execute(self, sql, parameters=()): + if self.debug: + tail = " with {!r}".format(parameters) if parameters else "" + self.debug.write("Executing {!r}{}".format(sql, tail)) + try: + return self.con.execute(sql, parameters) + except sqlite3.Error as exc: + raise CoverageException("Couldn't use data file {!r}: {}".format(self.filename, exc)) + + def executemany(self, sql, data): + if self.debug: + self.debug.write("Executing many {!r} with {} rows".format(sql, len(data))) + return self.con.executemany(sql, data) + + def dump(self): # pragma: debugging + """Return a multi-line string, the dump of the database.""" + return "\n".join(self.con.iterdump()) diff --git a/coverage/summary.py b/coverage/summary.py index 271b648a..95afbcf0 100644 --- a/coverage/summary.py +++ b/coverage/summary.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Summary reporting""" @@ -16,7 +16,8 @@ class SummaryReporter(Reporter): def __init__(self, coverage, config): super(SummaryReporter, self).__init__(coverage, config) - self.branches = coverage.data.has_arcs() + data = coverage.get_data() + self.branches = data.has_arcs() def report(self, morfs, outfile=None): """Writes a report summarizing coverage statistics per module. diff --git a/coverage/templite.py b/coverage/templite.py index 9944695a..b546ef7c 100644 --- a/coverage/templite.py +++ b/coverage/templite.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """A simple Python template renderer, for a nano-subset of Django syntax. diff --git a/coverage/version.py b/coverage/version.py index 7dc59e27..b5698037 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -1,11 +1,11 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """The version and URL for coverage.py""" # This file is exec'ed in setup.py, don't import anything! # Same semantics as sys.version_info. -version_info = (4, 5, 1, 'final', 0) +version_info = (5, 0, 0, 'alpha', 3) def _make_version(major, minor, micro, releaselevel, serial): diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index 3b651d46..5148b54a 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """XML reporting for coverage.py""" @@ -42,7 +42,8 @@ class XmlReporter(Reporter): self.source_paths.add(files.canonical_filename(src)) self.packages = {} self.xml_out = None - self.has_arcs = coverage.data.has_arcs() + self.data = coverage.get_data() + self.has_arcs = self.data.has_arcs() def report(self, morfs, outfile=None): """Generate a Cobertura-compatible XML report for `morfs`. diff --git a/doc/api.rst b/doc/api.rst index 26db06ef..d485d90a 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _api: diff --git a/doc/api_coverage.rst b/doc/api_coverage.rst index 70a57eef..33433605 100644 --- a/doc/api_coverage.rst +++ b/doc/api_coverage.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _api_coverage: @@ -12,7 +12,7 @@ The Coverage class .. autoclass:: Coverage :members: - :exclude-members: use_cache, sys_info + :exclude-members: sys_info :special-members: __init__ diff --git a/doc/api_coveragedata.rst b/doc/api_coveragedata.rst index 75215a3d..b3b64328 100644 --- a/doc/api_coveragedata.rst +++ b/doc/api_coveragedata.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _api_coveragedata: diff --git a/doc/api_plugin.rst b/doc/api_plugin.rst index e400a298..d070e2bf 100644 --- a/doc/api_plugin.rst +++ b/doc/api_plugin.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _api_plugin: diff --git a/doc/branch.rst b/doc/branch.rst index f95f1d47..92cab27b 100644 --- a/doc/branch.rst +++ b/doc/branch.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _branch: diff --git a/doc/changes.rst b/doc/changes.rst index 0243b5c1..1dd7b911 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _changes: diff --git a/doc/cmd.rst b/doc/cmd.rst index ef4c1135..86a858e4 100644 --- a/doc/cmd.rst +++ b/doc/cmd.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _cmd: @@ -69,7 +69,7 @@ control, and can provide options that other invocation techniques (like test runner plugins) may not offer. See :ref:`config` for more details. -.. _cmd_execution: +.. _cmd_run: Execution --------- @@ -118,18 +118,21 @@ configuration file for all options. .. _gevent: http://www.gevent.org/ .. _eventlet: http://eventlet.net/ +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. + +You can specify a :ref:`static context <contexts>` for a coverage run with +``--context``. This can be any label you want, and will be recorded with the +data. See :ref:`contexts` for more information. + By default, coverage.py 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`` (or ``--pylib``) flag. If your coverage results seem to be overlooking code that you know has been executed, try running coverage.py 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. - -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. +simpler but slower trace method, and might be needed in rare cases. .. _cmd_warnings: @@ -171,6 +174,13 @@ could affect the measurement process. The possible warnings include: when coverage started. This meant coverage.py couldn't monitor its execution. +* "Already imported a file that will be measured: XXX (already-imported)" + + File XXX had already been imported when coverage.py started measurement. Your + setting for ``--source`` or ``--include`` indicates that you wanted to + measure that file. Lines will be missing from the coverage report since the + execution during import hadn't been measured. + * "--include is ignored because --source is set (include-ignored)" Both ``--include`` and ``--source`` were specified while running code. Both @@ -479,6 +489,8 @@ to log: * ``process``: show process creation information, and changes in the current directory. +* ``self``: annotate each debug message with the object printing the message. + * ``sys``: before starting, dump all the system and environment information, as with :ref:`coverage debug sys <cmd_debug>`. diff --git a/doc/conf.py b/doc/conf.py index 08c88537..a87047eb 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # # coverage.py documentation build configuration file, created by @@ -56,9 +56,9 @@ copyright = u'2009\N{EN DASH}2018, Ned Batchelder' # CHANGEME # built documents. # # The short X.Y version. -version = '4.5' # CHANGEME +version = '5.0' # CHANGEME # The full version, including alpha/beta/rc tags. -release = '4.5.1' # CHANGEME +release = '5.0a2' # CHANGEME # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/config.rst b/doc/config.rst index c1fb4b1b..b8117a43 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _config: @@ -33,8 +33,9 @@ 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. -A different name for the configuration file can be specified with the -``--rcfile=FILE`` command line option. +A different location for the configuration file can be specified with the +``--rcfile=FILE`` command line option or with the ``COVERAGE_RCFILE`` +environment variable. Coverage.py will read settings from other usual configuration files if no other configuration file is used. It will automatically read from "setup.cfg" or @@ -58,8 +59,17 @@ or ``0`` and are case-insensitive. Environment variables can be substituted in by using dollar signs: ``$WORD`` or ``${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. +A dollar sign can be inserted with ``$$``. Special forms can be used to +control what happens if the variable isn't defined in the environment: + +- If you want to raise an error if an environment variable is undefined, use a + question mark suffix: ``${WORD?}``. + +- If you want to provide a default for missing variables, use a dash with a + default value: ``${WORD-default value}``. + +- Otherwise, 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>`. @@ -122,6 +132,11 @@ Before version 4.2, this option only accepted a single string. .. versionadded:: 4.0 +``context`` (string): the static context to record for this coverage run. See +:ref:`contexts` for more information + +.. versionadded:: 5.0 + ``data_file`` (string, default ".coverage"): the name of the data file to use for storing or reporting coverage. This value can include a path to another directory. diff --git a/doc/contexts.rst b/doc/contexts.rst new file mode 100644 index 00000000..1f1ce763 --- /dev/null +++ b/doc/contexts.rst @@ -0,0 +1,61 @@ +.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +.. _contexts: + +==================== +Measurement Contexts +==================== + +.. :history: 20180921T085400, new for 5.0 + +.. versionadded:: 5.0 + +Coverage.py measures whether code was run, but it can also record the context +in which it was run. This can provide more information to help you understand +the behavior of your tests. + +There are two kinds of context: static and dynamic. Static contexts are fixed +for an entire run, and are set explicitly with an option. Dynamic contexts +change over the course of a single run. + + +Static contexts +--------------- + +A static context is set by an option when you run coverage.py. The value is +fixed for the duration of a run. They can be any text you like, for example, +"python3" or "with_numpy". The context is recorded with the data. + +When you :ref:`combine multiple data files <cmd_combining>` together, they can +have differing contexts. All of the information is retained, so that the +different contexts are correctly recorded in the combined file. + +A static context is specified with the ``--context=CONTEXT`` option to +:ref:`coverage run <cmd_run>`. + + +Dynamic contexts +---------------- + +Dynamic contexts are found during execution. There is currently support for +one kind: test function names. Set the ``dynamic_context`` option to +``test_function`` in the configuration file:: + + [run] + dynamic_context = test_function + +Each test function you run will be considered a separate dynamic context, and +coverage data will be segregated for each. A test function is any function +whose names starts with "test". + +Ideas are welcome for other dynamic contexts that would be useful. + + +Context reporting +----------------- + +There is currently no support for using contexts during reporting. I'm +interested to `hear your ideas`__ for what would be useful. + +__ https://nedbatchelder.com/site/aboutned.html diff --git a/doc/contributing.rst b/doc/contributing.rst index 3f628109..90d73097 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _contributing: @@ -117,17 +117,34 @@ the second uses the C implementation. To limit tox to just a few versions of Python, use the ``-e`` switch:: - $ tox -e py27,py33 + $ tox -e py27,py37 To run just a few tests, you can use `pytest test selectors`_:: $ tox tests/test_misc.py - $ tox tests/test_misc.py::SetupPyTest - $ tox tests/test_misc.py::SetupPyTest::test_metadata + $ tox tests/test_misc.py::HasherTest + $ tox tests/test_misc.py::HasherTest::test_string_hashing These command run the tests in one file, one class, and just one test, respectively. +You can also affect the test runs with environment variables. Define any of +these as 1 to use them: + +- COVERAGE_NO_PYTRACER disables the Python tracer if you only want to run the + CTracer tests. + +- COVERAGE_NO_CTRACER disables the C tracer if you only want to run the + PyTracer tests. + +- COVEGE_AST_DUMP will dump the AST tree as it is being used during code + parsing. + +- COVERAGE_KEEP_TMP keeps the temporary directories in which tests are run. + This makes debugging tests easier. The temporary directories are at + ``$TMPDIR/coverage_test/*``, and are named for the test that made them. + + Of course, run all the tests on every version of Python you have, before submitting a change. diff --git a/doc/excluding.rst b/doc/excluding.rst index 465afe16..e56cf0a7 100644 --- a/doc/excluding.rst +++ b/doc/excluding.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _excluding: diff --git a/doc/faq.rst b/doc/faq.rst index a0145362..293b34b0 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _faq: @@ -109,15 +109,15 @@ __ https://nedbatchelder.com/blog/200710/flaws_in_coverage_measurement.html - `trialcoverage`_ is a plug-in for Twisted trial. - .. _trialcoverage: https://pypi.python.org/pypi/trialcoverage + .. _trialcoverage: https://pypi.org/project/trialcoverage/ - `pytest-coverage`_ - .. _pytest-coverage: https://pypi.python.org/pypi/pytest-coverage + .. _pytest-coverage: https://pypi.org/project/pytest-coverage/ - `django-coverage`_ for use with Django. - .. _django-coverage: https://pypi.python.org/pypi/django-coverage + .. _django-coverage: https://pypi.org/project/django-coverage/ **Q: Where can I get more help with coverage.py?** diff --git a/doc/howitworks.rst b/doc/howitworks.rst index 764472be..62af42e3 100644 --- a/doc/howitworks.rst +++ b/doc/howitworks.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _howitworks: diff --git a/doc/index.rst b/doc/index.rst index 1e70e30e..fc9f31c1 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt =========== Coverage.py @@ -77,21 +77,26 @@ not. .. ifconfig:: prerelease - The latest version is coverage.py 4.4b1, released April 4th 2017. It is - supported on: + The latest version is coverage.py 5.0a2, released September 3rd 2018. + It is supported on: - * Python versions 2.6, 2.7, 3.3, 3.4, 3.5, and 3.6. + * Python versions 2.7, 3.4, 3.5, 3.6, and 3.7. - * PyPy2 5.6 and PyPy3 5.5. + * PyPy2 6.0 and PyPy3 6.0. * Jython 2.7.1, though only for running code, not reporting. * IronPython 2.7.7, though only for running code, not reporting. **This is a pre-release build. The usual warnings about possible bugs - apply.** The latest stable version is coverage.py 4.3.4, `described here`_. + apply.** The latest stable version is coverage.py 4.5.1, `described here`_. -.. _described here: https://nedbatchelder.com/code/coverage +.. _described here: http://coverage.readthedocs.io/ + +Professional support for coverage.py is available as part of the `Tidelift +Subscription`_. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=docs .. image:: media/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White.png :width: 75 @@ -159,7 +164,7 @@ Getting started is easy: Then visit htmlcov/index.html in your browser, to see a `report like this one`_. -.. _coverage.py page on the Python Package Index: https://pypi.python.org/pypi/coverage +.. _coverage.py page on the Python Package Index: https://pypi.org/project/coverage/ .. _report like this: https://nedbatchelder.com/files/sample_coverage_html/index.html .. _report like this one: https://nedbatchelder.com/files/sample_coverage_html_beta/index.html @@ -223,6 +228,7 @@ More information excluding branch subprocess + contexts api howitworks plugins diff --git a/doc/install.rst b/doc/install.rst index 29bc833e..62bd0b98 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _install: @@ -29,8 +29,8 @@ Installation .. highlight:: console -.. _coverage_pypi: https://pypi.python.org/pypi/coverage -.. _setuptools: https://pypi.python.org/pypi/setuptools +.. _coverage_pypi: https://pypi.org/project/coverage/ +.. _setuptools: https://pypi.org/project/setuptools/ You can install coverage.py in the usual ways. The simplest way is with pip:: @@ -95,36 +95,19 @@ Checking the installation If all went well, you should be able to open a command prompt, and see coverage.py installed properly: -.. ifconfig:: not prerelease +.. In the output below, the URL should actually have the release in it for + pre-release, but Sphinx couldn't make a URL like that, so whatever. - .. parsed-literal:: +.. parsed-literal:: - $ coverage --version - Coverage.py, version |release| with C extension - Documentation at https://coverage.readthedocs.io - -.. ifconfig:: prerelease - - .. parsed-literal:: - - $ coverage --version - Coverage.py, version |release| with C extension - Documentation at https://coverage.readthedocs.io/en/coverage-|release| + $ coverage --version + Coverage.py, version |release| with C extension + Documentation at https://coverage.readthedocs.io You can also invoke coverage.py as a module: -.. ifconfig:: not prerelease +.. parsed-literal:: - .. parsed-literal:: - - $ python -m coverage --version - Coverage.py, version |release| with C extension - Documentation at https://coverage.readthedocs.io - -.. ifconfig:: prerelease - - .. parsed-literal:: - - $ python -m coverage --version - Coverage.py, version |release| with C extension - Documentation at https://coverage.readthedocs.io/en/coverage-|release| + $ python -m coverage --version + Coverage.py, version |release| with C extension + Documentation at https://coverage.readthedocs.io diff --git a/doc/plugins.rst b/doc/plugins.rst index f2bad6d4..c77c1e33 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _plugins: @@ -66,7 +66,7 @@ Some coverage.py plug-ins you might find useful: * `Django template coverage.py plug-in`__: for measuring coverage in Django templates. - .. __: https://pypi.python.org/pypi/django_coverage_plugin + .. __: https://pypi.org/project/django_coverage_plugin/ * `Mako template coverage plug-in`__: for measuring coverage in Mako templates. Doesn't work yet, probably needs some changes in Mako itself. diff --git a/doc/python-coverage.1.txt b/doc/python-coverage.1.txt index 94402b84..a415f080 100644 --- a/doc/python-coverage.1.txt +++ b/doc/python-coverage.1.txt @@ -8,7 +8,7 @@ measure code coverage of Python program execution :Author: Ned Batchelder <ned@nedbatchelder.com> :Author: |author| -:Date: 2015-09-20 +:Date: 2018-05-28 :Copyright: Apache 2.0 license, attribution and disclaimer required. :Manual section: 1 :Manual group: Coverage.py @@ -219,6 +219,10 @@ COVERAGE_FILE Path to the file where coverage measurements are collected to and reported from. Default: ``.coverage`` in the current working directory. +COVERAGE_RCFILE + + Path to the configuration file, often named ``.coveragerc``. + HISTORY ======= diff --git a/doc/requirements.pip b/doc/requirements.pip index 73467c94..71b17134 100644 --- a/doc/requirements.pip +++ b/doc/requirements.pip @@ -3,9 +3,9 @@ # https://requires.io/github/nedbat/coveragepy/requirements/ pyenchant==2.0.0 -sphinx==1.6.6 -sphinxcontrib-spelling==4.0.1 -sphinx_rtd_theme==0.2.4 +sphinx==1.7.6 +sphinxcontrib-spelling==4.2.0 +sphinx_rtd_theme==0.4.1 # A version of doc8 with a -q flag. git+https://github.com/nedbat/doc8.git#egg=doc8==0.0 diff --git a/doc/source.rst b/doc/source.rst index 640fc431..e1bc8038 100644 --- a/doc/source.rst +++ b/doc/source.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _source: diff --git a/doc/subprocess.rst b/doc/subprocess.rst index 7236b7ef..06068959 100644 --- a/doc/subprocess.rst +++ b/doc/subprocess.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _subprocess: diff --git a/doc/trouble.rst b/doc/trouble.rst index e3951218..16483bb2 100644 --- a/doc/trouble.rst +++ b/doc/trouble.rst @@ -1,5 +1,5 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt .. _trouble: @@ -53,21 +53,6 @@ coverage.py from working properly: .. _issue 43: https://bitbucket.org/ned/coveragepy/issues/43/coverage-measurement-fails-on-code -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: https://pypi.python.org/pypi/DecoratorTools -.. _TurboGears: http://turbogears.org/ - - Still having trouble? --------------------- @@ -10,6 +10,7 @@ - Update CHANGES.rst, including release date. - Update README.rst - "New in x.y:" + - Python versions supported - Update docs - Version, date and python versions in doc/index.rst - Version and copyright date in doc/conf.py @@ -18,7 +19,7 @@ - Check that the docs build correctly: $ tox -e doc - Done with changes to source files, check them in. - - hg push + - git push - Generate new sample_html to get the latest, incl footer version number: make clean pip install -e . @@ -54,13 +55,12 @@ - https://ci.appveyor.com/project/nedbat/coveragepy - $ make download_appveyor - examine the dist directory, and remove anything that looks malformed. -- Update PyPi: +- Update PyPI: - upload kits: - $ make kit_upload - - Visit https://pypi.python.org/pypi?:action=pkg_edit&name=coverage : - - show/hide the proper versions. - Tag the tree - - hg tag -m "Coverage 3.0.1" coverage-3.0.1 + - git tag coverage-3.0.1 + - git push --tags - Bump version: - coverage/version.py - increment version number @@ -68,19 +68,23 @@ - set to alpha-0 if just released - CHANGES.rst - add an "Unreleased" section to the top. -- push hg changes +- push git changes - Update nedbatchelder.com - Blog post? - Update readthedocs - visit https://readthedocs.org/projects/coverage/versions/ - find the latest tag in the inactive list, edit it, make it active. + - keep just the latest version of each x.y release, make the rest inactive. - IF NOT BETA: + - visit https://readthedocs.org/projects/coverage/builds/ + - wait for the new tag build to finish successfully. - visit https://readthedocs.org/dashboard/coverage/versions/ - change the default version to the new version - Update bitbucket: - Issue tracker should get new version number in picker. # Note: don't delete old version numbers: it marks changes on the tickets # with that number. +- Visit the fixed issues on bitbucket and mention the version it was fixed in. - Announce on coveragepy-announce@googlegroups.com . - Announce on TIP. @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Helper for building, testing, and linting coverage.py. @@ -122,11 +122,8 @@ def run_tests_with_coverage(tracer, *runner_args): import coverage cov = coverage.Coverage(config_file="metacov.ini", data_suffix=False) - # Cheap trick: the coverage.py 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._warn_unimported_source = False + cov._warn_preimported_source = False cov.start() try: @@ -290,14 +287,12 @@ def do_check_eol(): check_files("coverage", ["*.py"]) check_files("coverage/ctracer", ["*.c", "*.h"]) check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"]) - check_file("tests/farm/html/src/bom.py", crlf=False) check_files("tests", ["*.py"]) check_files("tests", ["*,cover"], trail_white=False) check_files("tests/js", ["*.js", "*.html"]) check_file("setup.py") check_file("igor.py") check_file("Makefile") - check_file(".hgignore") check_file(".travis.yml") check_files(".", ["*.rst", "*.txt"]) check_files(".", ["*.pip"]) diff --git a/lab/README.txt b/lab/README.txt new file mode 100644 index 00000000..3263667a --- /dev/null +++ b/lab/README.txt @@ -0,0 +1,4 @@ +The lab directory is not part of the installed coverage.py code. These programs +are tools I have used while diagnosing problems, investigating functionality, +and so on. They are not guaranteed to work, or to be suitable for any given +purpose. If you find them useful, enjoy! diff --git a/lab/branches.py b/lab/branches.py index d1908d0f..c2b838dd 100644 --- a/lab/branches.py +++ b/lab/branches.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Demonstrate some issues with coverage.py branch testing. @@ -13,11 +13,11 @@ def my_function(x): i = 0 while True: - print "In while True" + print("In while True") if i > 0: break i += 1 - print "Left the True loop" + print("Left the True loop") # Notice that "while 1" also has this problem. Even though the compiler # knows there's no computation at the top of the loop, it's still expressed @@ -25,11 +25,11 @@ def my_function(x): i = 0 while 1: - print "In while 1" + print("In while 1") if i > 0: break i += 1 - print "Left the 1 loop" + print("Left the 1 loop") # Coverage.py lets developers exclude lines that they know will not be # executed. So far, the branch coverage doesn't use all that information @@ -40,9 +40,9 @@ def my_function(x): if x < 1000: # This branch is always taken - print "x is reasonable" + print("x is reasonable") else: # pragma: nocover - print "this never happens" + print("this never happens") # try-except structures are complex branches. An except clause with a # type is a three-way branch: there could be no exception, there could be @@ -57,9 +57,9 @@ def my_function(x): if y % 2: raise ValueError("y is odd!") except ValueError: - print "y must have been odd" - print "done with y" - print "done with 1, 2" + print("y must have been odd") + print("done with y") + print("done with 1, 2") # Another except clause, but this time all three cases are executed. No # partial lines are shown: @@ -71,11 +71,11 @@ def my_function(x): if y == 0: raise Exception("zero!") except ValueError: - print "y must have been odd" + print("y must have been odd") except: - print "y is something else" - print "done with y" - print "done with 0, 1, 2" + print("y is something else") + print("done with y") + print("done with 0, 1, 2") my_function(1) diff --git a/lab/find_class.py b/lab/find_class.py new file mode 100644 index 00000000..d8dac0b5 --- /dev/null +++ b/lab/find_class.py @@ -0,0 +1,40 @@ +class Parent(object): + def meth(self): + print("METH") + +class Child(Parent): + pass + +def trace(frame, event, args): + # Thanks to Aleksi Torhamo for code and idea. + co = frame.f_code + fname = co.co_name + if not co.co_varnames: + return + locs = frame.f_locals + first_arg = co.co_varnames[0] + if co.co_argcount: + self = locs[first_arg] + elif co.co_flags & 0x04: # *args syntax + self = locs[first_arg][0] + else: + return + + func = getattr(self, fname).__func__ + if hasattr(func, '__qualname__'): + qname = func.__qualname__ + else: + for cls in self.__class__.__mro__: + f = cls.__dict__.get(fname, None) + if f is None: + continue + if f is func: + qname = cls.__name__ + "." + fname + break + print("{}: {}.{} {}".format(event, self, fname, qname)) + return trace + +import sys +sys.settrace(trace) + +Child().meth() diff --git a/lab/gendata.py b/lab/gendata.py new file mode 100644 index 00000000..27ad4fda --- /dev/null +++ b/lab/gendata.py @@ -0,0 +1,42 @@ +# Run some timing tests of JsonData vs SqliteData. + +import random +import time + +from coverage.data import CoverageJsonData +from coverage.sqldata import CoverageSqliteData + +NUM_FILES = 1000 +NUM_LINES = 1000 + +def gen_data(cdata): + rnd = random.Random() + rnd.seed(17) + + def linenos(num_lines, prob): + return (n for n in range(num_lines) if random.random() < prob) + + start = time.time() + for i in range(NUM_FILES): + filename = "/src/foo/project/file{i}.py".format(i=i) + line_data = { filename: dict.fromkeys(linenos(NUM_LINES, .6)) } + cdata.add_lines(line_data) + + cdata.write() + end = time.time() + delta = end - start + return delta + +class DummyData: + def add_lines(self, line_data): + return + def write(self): + return + +overhead = gen_data(DummyData()) +jtime = gen_data(CoverageJsonData("gendata.json")) - overhead +stime = gen_data(CoverageSqliteData("gendata.db")) - overhead +print("Overhead: {overhead:.3f}s".format(overhead=overhead)) +print("JSON: {jtime:.3f}s".format(jtime=jtime)) +print("SQLite: {stime:.3f}s".format(stime=stime)) +print("{slower:.3f}x slower".format(slower=stime/jtime)) diff --git a/lab/hack_pyc.py b/lab/hack_pyc.py index 0ebd9948..d63da8fa 100644 --- a/lab/hack_pyc.py +++ b/lab/hack_pyc.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """ Wicked hack to get .pyc files to do bytecode tracing instead of line tracing. diff --git a/lab/parser.py b/lab/parser.py index 0393d209..b3560506 100644 --- a/lab/parser.py +++ b/lab/parser.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Parser.py: a main for invoking code in coverage/parser.py""" diff --git a/lab/platform_info.py b/lab/platform_info.py index 8caea50f..61e02dd2 100644 --- a/lab/platform_info.py +++ b/lab/platform_info.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Dump information so we can get a quick look at what's available.""" diff --git a/lab/run_trace.py b/lab/run_trace.py index ea0a6cb7..27c24a1d 100644 --- a/lab/run_trace.py +++ b/lab/run_trace.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Run a simple trace function on a file of Python code.""" @@ -14,13 +14,13 @@ def trace(frame, event, arg): # This can happen when Python is shutting down. return None - print "%s%s %s %d @%d" % ( + print("%s%s %s %d @%d" % ( " " * nest, event, os.path.basename(frame.f_code.co_filename), frame.f_lineno, frame.f_lasti, - ) + )) if event == 'call': nest += 1 diff --git a/lab/show_platform.py b/lab/show_platform.py index 76122d58..e4f4dc2a 100644 --- a/lab/show_platform.py +++ b/lab/show_platform.py @@ -13,4 +13,4 @@ for n in dir(platform): n += "()" except: continue - print "%30s: %r" % (n, v) + print("%30s: %r" % (n, v)) diff --git a/lab/show_pyc.py b/lab/show_pyc.py index 525797a8..7573c1c3 100644 --- a/lab/show_pyc.py +++ b/lab/show_pyc.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt import binascii import dis diff --git a/metacov.ini b/metacov.ini index 55d0225e..50ea524d 100644 --- a/metacov.ini +++ b/metacov.ini @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Settings to use when using coverage.py to measure itself. [run] @@ -35,6 +35,10 @@ exclude_lines = # OS error conditions that we can't (or don't care to) replicate. pragma: cant happen + # Obscure bugs in specific versions of interpreters, and so probably no + # longer tested. + pragma: obscure + # Jython needs special care. pragma: only jython skip.*Jython diff --git a/perf/perf_measure.py b/perf/perf_measure.py index 3b0ae52a..a8f2ffaa 100644 --- a/perf/perf_measure.py +++ b/perf/perf_measure.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Run like this: # .tox/py36/bin/python perf/perf_measure.py @@ -78,7 +78,7 @@ class StressTest(object): finally: # pragma: nested # Stop coverage.py. covered = time.perf_counter() - start - stats = cov.collector.tracers[0].get_stats() + stats = cov._collector.tracers[0].get_stats() if stats: stats = stats.copy() cov.stop() diff --git a/perf/solve_poly.py b/perf/solve_poly.py index 41365f48..66231725 100644 --- a/perf/solve_poly.py +++ b/perf/solve_poly.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Given empirical data from perf_measure.py, calculate the coefficients of the # polynomials for file, call, and line operation counts. @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # lint Python modules using external checkers. # @@ -74,9 +74,6 @@ disable= too-many-ancestors, # Formatting stuff superfluous-parens,bad-continuation, -# I'm fine deciding my own import order, - wrong-import-position, - wrong-import-order, # Messages that are noisy for now, eventually maybe we'll turn them on: invalid-name, protected-access, @@ -133,9 +130,9 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # Special methods don't: __foo__ # Test methods don't: testXXXX # TestCase overrides don't: setUp, tearDown -# Nested decorator implementations: _decorator, _wrapped +# Nested decorator implementations: _decorator, _wrapper # Dispatched methods don't: _xxx__Xxxx -no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_decorator|_wrapped|_.*__.* +no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_decorator|_wrapper|_.*__.* # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -327,13 +324,3 @@ ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes - -# -# SPELLING -# - -spelling-dict=en_US -# pylint doesn't strip the words, so insert a dummy x at the beginning to make -# the other words work properly. -# https://bitbucket.org/logilab/pylint/issue/398/spelling-words-need-to-be-stripped-or-the -spelling-private-dict-file=doc/dict.txt diff --git a/requirements/ci.pip b/requirements/ci.pip index 12e9c698..cb94d737 100644 --- a/requirements/ci.pip +++ b/requirements/ci.pip @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Things CI servers need to succeeed. -r tox.pip diff --git a/requirements/dev.pip b/requirements/dev.pip index 183d051f..c2cfcb1b 100644 --- a/requirements/dev.pip +++ b/requirements/dev.pip @@ -1,21 +1,21 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Requirements for doing local development work on coverage.py. # https://requires.io/github/nedbat/coveragepy/requirements/ +pip==18.0.0 + # PyPI requirements for running tests. -r tox.pip -r pytest.pip # for linting. -greenlet==0.4.13 -mock==2.0.0 -PyContracts==1.8.2 -pyenchant==2.0.0 -pylint==1.8.2 -unittest-mixins==1.4 +greenlet==0.4.14 +pylint==1.9.2 +check-manifest==0.37 +readme_renderer==21.0 # for kitting. -requests==2.18.4 -twine==1.9.1 +requests==2.19.1 +twine==1.11.0 diff --git a/requirements/pytest.pip b/requirements/pytest.pip index 17ccc0d8..a946a59a 100644 --- a/requirements/pytest.pip +++ b/requirements/pytest.pip @@ -1,9 +1,15 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # The pytest specifics used by coverage.py -# Keep pytest at 3.2.x until we are done with Python 2.6 and 3.3 -pytest==3.2.5 -pytest-xdist==1.20.1 +pytest==3.8.0 +pluggy>=0.7 # pytest needs this, but pip doesn't understand +pytest-xdist==1.23.0 flaky==3.4.0 +mock==2.0.0 +PyContracts==1.8.3 + +# Our testing mixins +unittest-mixins==1.6 +#-e/Users/ned/unittest_mixins diff --git a/requirements/tox.pip b/requirements/tox.pip index b57aa388..07d889e6 100644 --- a/requirements/tox.pip +++ b/requirements/tox.pip @@ -1,4 +1,4 @@ # The version of tox used by coverage.py -tox==2.9.1 +tox==3.2.1 # Adds env recreation on requirements file changes. -tox-battery==0.5 +tox-battery==0.5.1 diff --git a/requirements/wheel.pip b/requirements/wheel.pip index 6dfe70b2..30ac5455 100644 --- a/requirements/wheel.pip +++ b/requirements/wheel.pip @@ -1,4 +1,3 @@ # Things needed to make wheels for coverage.py -setuptools==35.0.2 -# We need to stick with 0.29.0 until we drop 2.6 and 3.3 -wheel==0.29.0 +setuptools==40.0.0 +wheel==0.31.1 @@ -1,5 +1,5 @@ [tool:pytest] -addopts = -q -n3 --strict --no-flaky-report +addopts = -q -n3 --strict --no-flaky-report -rfe --failed-first markers = expensive: too slow to run during "make smoke" @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Code coverage measurement for Python""" @@ -9,10 +9,12 @@ import os import sys +# Setuptools has to be imported before distutils, or things break. from setuptools import setup -from distutils.core import Extension # pylint: disable=no-name-in-module, import-error -from distutils.command.build_ext import build_ext # pylint: disable=no-name-in-module, import-error -from distutils import errors # pylint: disable=no-name-in-module +from distutils.core import Extension # pylint: disable=no-name-in-module, import-error, wrong-import-order +from distutils.command.build_ext import build_ext # pylint: disable=no-name-in-module, import-error, wrong-import-order +from distutils import errors # pylint: disable=no-name-in-module, wrong-import-order + # Get or massage our metadata. We exec coverage/version.py so we can avoid # importing the product code into setup.py. @@ -24,10 +26,8 @@ License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 -Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 -Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 @@ -105,9 +105,9 @@ setup_args = dict( keywords='code coverage testing', license='Apache 2.0', classifiers=classifier_list, - url="https://bitbucket.org/ned/coveragepy", + url="https://github.com/nedbat/coveragepy", - python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4", + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4", ) # A replacement for the build_ext command which raises a single exception diff --git a/tests/__init__.py b/tests/__init__.py index 1ff1e1be..38f2ff8b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Automated tests. Run with pytest.""" diff --git a/tests/backtest.py b/tests/backtest.py index 827e891f..979f6755 100644 --- a/tests/backtest.py +++ b/tests/backtest.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Add things to old Pythons so I can pretend they are newer, for tests.""" diff --git a/tests/conftest.py b/tests/conftest.py index a0c21a84..e9802517 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """ Pytest auto configuration. @@ -7,12 +7,29 @@ Pytest auto configuration. This module is run automatically by pytest, to define and enable fixtures. """ -import pytest import warnings +import pytest + +from coverage import env + @pytest.fixture(autouse=True) def set_warnings(): """Enable DeprecationWarnings during all tests.""" warnings.simplefilter("default") warnings.simplefilter("once", DeprecationWarning) + + # A warning to suppress: + # setuptools/py33compat.py:54: DeprecationWarning: The value of convert_charrefs will become + # True in 3.5. You are encouraged to set the value explicitly. + # unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape) + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message="The value of convert_charrefs will become True in 3.5.", + ) + if env.PYPY and env.PY3: + # pypy3 warns about unclosed files a lot. + # pylint: disable=undefined-variable + warnings.filterwarnings("ignore", r".*unclosed file", category=ResourceWarning) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 29d634c4..378097c8 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -1,11 +1,12 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Base test case class for coverage.py testing.""" import contextlib import datetime import functools +import glob import os import random import re @@ -23,7 +24,7 @@ from coverage import env from coverage.backunittest import TestCase, unittest from coverage.backward import StringIO, import_local_file, string_class, shlex_quote from coverage.cmdline import CoverageScript -from coverage.debug import _TEST_NAME_FILE +from coverage.data import STORAGE from coverage.misc import StopEverything from tests.helpers import run_command, SuperModuleCleaner @@ -39,14 +40,13 @@ TESTS_DIR = os.path.dirname(__file__) def convert_skip_exceptions(method): """A decorator for test methods to convert StopEverything to SkipTest.""" @functools.wraps(method) - def wrapper(*args, **kwargs): - """Run the test method, and convert exceptions.""" + def _wrapper(*args, **kwargs): try: result = method(*args, **kwargs) except StopEverything: raise unittest.SkipTest("StopEverything!") return result - return wrapper + return _wrapper class SkipConvertingMetaclass(type): @@ -80,6 +80,12 @@ class CoverageTest( # Let stderr go to stderr, pytest will capture it for us. show_stderr = True + # Temp dirs go to $TMPDIR/coverage_test/* + temp_dir_prefix = "coverage_test/" + + # Keep the temp directories if the env says to. + keep_temp_dir = bool(int(os.getenv("COVERAGE_KEEP_TMP", 0))) + def setUp(self): super(CoverageTest, self).setUp() @@ -90,11 +96,9 @@ class CoverageTest( self.last_command_output = None self.last_module_name = None - if _TEST_NAME_FILE: # pragma: debugging - with open(_TEST_NAME_FILE, "w") as f: - f.write("%s_%s" % ( - self.__class__.__name__, self._testMethodName, - )) + def skip_unless_data_storage_is(self, storage): + if STORAGE != storage: + self.skipTest("Not using {} for data storage".format(storage)) def clean_local_file_imports(self): """Clean up the results of calls to `import_local_file`. @@ -347,6 +351,13 @@ class CoverageTest( msg = "File %r shouldn't exist" % fname self.assertTrue(not os.path.exists(fname), msg) + def assert_file_count(self, pattern, count): + """Assert that there are `count` files matching `pattern`.""" + files = glob.glob(pattern) + msg = "There should be {} files matching {!r}, but there are these: {}" + msg = msg.format(count, pattern, files) + self.assertEqual(len(files), count, msg) + def assert_starts_with(self, s, prefix, msg=None): """Assert that `s` starts with `prefix`.""" if not s.startswith(prefix): @@ -355,10 +366,8 @@ class CoverageTest( def assert_recent_datetime(self, dt, seconds=10, msg=None): """Assert that `dt` marks a time at most `seconds` seconds ago.""" age = datetime.datetime.now() - dt - # Python2.6 doesn't have total_seconds :( - self.assertEqual(age.days, 0, msg) - self.assertGreaterEqual(age.seconds, 0, msg) - self.assertLessEqual(age.seconds, seconds, msg) + self.assertGreaterEqual(age.total_seconds(), 0, msg) + self.assertLessEqual(age.total_seconds(), seconds, msg) def command_line(self, args, ret=OK, _covpkg=None): """Run `args` through the command line. diff --git a/tests/covmodzip1.py b/tests/covmodzip1.py index cab64281..0133ed6a 100644 --- a/tests/covmodzip1.py +++ b/tests/covmodzip1.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Module-level docstrings are counted differently in different versions of Python, # so don't add one here. diff --git a/tests/eggsrc/egg1/egg1.py b/tests/eggsrc/egg1/egg1.py index 72600808..939386e3 100644 --- a/tests/eggsrc/egg1/egg1.py +++ b/tests/eggsrc/egg1/egg1.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # My egg file! diff --git a/tests/eggsrc/setup.py b/tests/eggsrc/setup.py index c935798d..26a0b650 100644 --- a/tests/eggsrc/setup.py +++ b/tests/eggsrc/setup.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt from setuptools import setup diff --git a/tests/farm/annotate/annotate_dir.py b/tests/farm/annotate/annotate_dir.py index 3bb2dbe7..9bf1b768 100644 --- a/tests/farm/annotate/annotate_dir.py +++ b/tests/farm/annotate/annotate_dir.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt copy("src", "run") run(""" diff --git a/tests/farm/annotate/gold/white.py,cover b/tests/farm/annotate/gold/white.py,cover index fc163226..b190ffdb 100644 --- a/tests/farm/annotate/gold/white.py,cover +++ b/tests/farm/annotate/gold/white.py,cover @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # A test case sent to me by Steve White diff --git a/tests/farm/annotate/gold_anno_dir/a_a.py,cover b/tests/farm/annotate/gold_anno_dir/a_a.py,cover index 4729cfbb..0bd0f4a5 100644 --- a/tests/farm/annotate/gold_anno_dir/a_a.py,cover +++ b/tests/farm/annotate/gold_anno_dir/a_a.py,cover @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt > def a(x): > if x == 1: diff --git a/tests/farm/annotate/gold_anno_dir/b_b.py,cover b/tests/farm/annotate/gold_anno_dir/b_b.py,cover index 228715f0..49f723ac 100644 --- a/tests/farm/annotate/gold_anno_dir/b_b.py,cover +++ b/tests/farm/annotate/gold_anno_dir/b_b.py,cover @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt > def b(x): > msg = "x is %s" % x diff --git a/tests/farm/annotate/gold_anno_dir/multi.py,cover b/tests/farm/annotate/gold_anno_dir/multi.py,cover index 90a13c91..980f4793 100644 --- a/tests/farm/annotate/gold_anno_dir/multi.py,cover +++ b/tests/farm/annotate/gold_anno_dir/multi.py,cover @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt > import a.a > import b.b diff --git a/tests/farm/annotate/gold_encodings/utf8.py,cover b/tests/farm/annotate/gold_encodings/utf8.py,cover index 3ef31e0f..0bd26415 100644 --- a/tests/farm/annotate/gold_encodings/utf8.py,cover +++ b/tests/farm/annotate/gold_encodings/utf8.py,cover @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # This comment has an accent: é diff --git a/tests/farm/annotate/gold_multi/a/a.py,cover b/tests/farm/annotate/gold_multi/a/a.py,cover index e5e97226..95f0d8c5 100644 --- a/tests/farm/annotate/gold_multi/a/a.py,cover +++ b/tests/farm/annotate/gold_multi/a/a.py,cover @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt > def a(x): > if x == 1: diff --git a/tests/farm/annotate/gold_multi/b/b.py,cover b/tests/farm/annotate/gold_multi/b/b.py,cover index 26b25548..18240bf0 100644 --- a/tests/farm/annotate/gold_multi/b/b.py,cover +++ b/tests/farm/annotate/gold_multi/b/b.py,cover @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt > def b(x): > print "x is %s" % x diff --git a/tests/farm/annotate/gold_multi/multi.py,cover b/tests/farm/annotate/gold_multi/multi.py,cover index 90a13c91..980f4793 100644 --- a/tests/farm/annotate/gold_multi/multi.py,cover +++ b/tests/farm/annotate/gold_multi/multi.py,cover @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 - # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt > import a.a > import b.b diff --git a/tests/farm/annotate/run.py b/tests/farm/annotate/run.py index 33e5f671..6c98a770 100644 --- a/tests/farm/annotate/run.py +++ b/tests/farm/annotate/run.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt copy("src", "out") run(""" diff --git a/tests/farm/annotate/run_encodings.py b/tests/farm/annotate/run_encodings.py index 46d8c643..8fd98fea 100644 --- a/tests/farm/annotate/run_encodings.py +++ b/tests/farm/annotate/run_encodings.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt copy("src", "out_encodings") run(""" diff --git a/tests/farm/annotate/run_multi.py b/tests/farm/annotate/run_multi.py index 4646293e..d3c4cf7c 100644 --- a/tests/farm/annotate/run_multi.py +++ b/tests/farm/annotate/run_multi.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt copy("src", "out_multi") run(""" diff --git a/tests/farm/annotate/src/a/a.py b/tests/farm/annotate/src/a/a.py index e3e6631d..411d3d8e 100644 --- a/tests/farm/annotate/src/a/a.py +++ b/tests/farm/annotate/src/a/a.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt def a(x): if x == 1: diff --git a/tests/farm/annotate/src/b/b.py b/tests/farm/annotate/src/b/b.py index b31d8c95..f0055b3b 100644 --- a/tests/farm/annotate/src/b/b.py +++ b/tests/farm/annotate/src/b/b.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt def b(x): msg = "x is %s" % x diff --git a/tests/farm/annotate/src/multi.py b/tests/farm/annotate/src/multi.py index bf8cfd5f..31155b40 100644 --- a/tests/farm/annotate/src/multi.py +++ b/tests/farm/annotate/src/multi.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt import a.a import b.b diff --git a/tests/farm/annotate/src/utf8.py b/tests/farm/annotate/src/utf8.py index fd43b2ab..f3988379 100644 --- a/tests/farm/annotate/src/utf8.py +++ b/tests/farm/annotate/src/utf8.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # This comment has an accent: é diff --git a/tests/farm/annotate/src/white.py b/tests/farm/annotate/src/white.py index 21e8a627..56608324 100644 --- a/tests/farm/annotate/src/white.py +++ b/tests/farm/annotate/src/white.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # A test case sent to me by Steve White diff --git a/tests/farm/html/gold_a/a_py.html b/tests/farm/html/gold_a/a_py.html index 00ab529d..119ad4a3 100644 --- a/tests/farm/html/gold_a/a_py.html +++ b/tests/farm/html/gold_a/a_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 67%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,90 +14,69 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>a.py</b> : <span class="pc_cov">67%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 3 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">1 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<p id="n1" class="pln"><a href="#n1">1</a></p> +<p id="n1" class="stm run hide_run"><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="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="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="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="pln"><a href="#n9">9</a></p> -<p id="n10" class="stm mis"><a href="#n10">10</a></p> - +<p id="n5" class="stm mis"><a href="#n5">5</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="com"># A test file for HTML reporting by coverage.py.</span><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">if</span> <span class="num">1</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t7" class="pln"> <span class="com"># Needed a < to look at HTML entities.</span><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">3</span><span class="strut"> </span></p> -<p id="t9" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t10" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> - +<p id="t1" class="stm run hide_run"><span class="key">if</span> <span class="num">1</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t2" class="pln"> <span class="com"># Needed a < to look at HTML entities.</span><span class="strut"> </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"> </span></p> +<p id="t4" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t5" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_a/index.html b/tests/farm/html/gold_a/index.html index 35507b6f..b839af1e 100644 --- a/tests/farm/html/gold_a/index.html +++ b/tests/farm/html/gold_a/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,63 +34,51 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="2 3">67%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="a_py.html">a.py</a></td> <td>3</td> <td>1</td> <td>0</td> - <td class="right" data-ratio="2 3">67%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_b_branch/b_py.html b/tests/farm/html/gold_b_branch/b_py.html index 4a876500..a21175eb 100644 --- a/tests/farm/html/gold_b_branch/b_py.html +++ b/tests/farm/html/gold_b_branch/b_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 70%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,136 +14,114 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>b.py</b> : <span class="pc_cov">70%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 17 statements <span class="run hide_run shortkey_r button_toggle_run">14 run</span> <span class="mis shortkey_m button_toggle_mis">3 missing</span> <span class="exc shortkey_x button_toggle_exc">0 excluded</span> - - <span class="par run hide_run shortkey_p button_toggle_par">4 partial</span> - </h2> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<p id="n1" class="pln"><a href="#n1">1</a></p> +<p id="n1" class="stm run hide_run"><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="pln"><a href="#n4">4</a></p> +<p id="n3" class="stm par run hide_run"><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="n6" class="stm mis"><a href="#n6">6</a></p> <p id="n7" class="pln"><a href="#n7">7</a></p> -<p id="n8" class="stm par 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 mis"><a href="#n11">11</a></p> -<p id="n12" class="pln"><a href="#n12">12</a></p> +<p id="n8" class="stm run hide_run"><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 par run hide_run"><a href="#n12">12</a></p> <p id="n13" class="stm run hide_run"><a href="#n13">13</a></p> <p id="n14" class="pln"><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 par run hide_run"><a href="#n17">17</a></p> +<p id="n17" class="stm run hide_run"><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> -<p id="n20" class="stm run hide_run"><a href="#n20">20</a></p> -<p id="n21" class="pln"><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 par run hide_run"><a href="#n25">25</a></p> -<p id="n26" class="stm mis"><a href="#n26">26</a></p> -<p id="n27" class="pln"><a href="#n27">27</a></p> -<p id="n28" class="stm mis"><a href="#n28">28</a></p> -<p id="n29" class="stm run hide_run"><a href="#n29">29</a></p> -<p id="n30" class="stm run hide_run"><a href="#n30">30</a></p> -<p id="n31" class="pln"><a href="#n31">31</a></p> -<p id="n32" class="stm run hide_run"><a href="#n32">32</a></p> - +<p id="n20" class="stm par run hide_run"><a href="#n20">20</a></p> +<p id="n21" class="stm mis"><a href="#n21">21</a></p> +<p id="n22" class="pln"><a href="#n22">22</a></p> +<p id="n23" class="stm mis"><a href="#n23">23</a></p> +<p id="n24" class="stm run hide_run"><a href="#n24">24</a></p> +<p id="n25" class="stm run hide_run"><a href="#n25">25</a></p> +<p id="n26" class="pln"><a href="#n26">26</a></p> +<p id="n27" class="stm run hide_run"><a href="#n27">27</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="com"># A test file for HTML reporting by coverage.py.</span><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="strut"> </span></p> -<p id="t6" 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"> </span></p> -<p id="t7" class="pln"> <span class="com"># This will be a branch that misses the else.</span><span class="strut"> </span></p> -<p id="t8" class="stm par run hide_run"><span class="annotate" title="no jump to this line number">11</span> <span class="key">if</span> <span class="nam">x</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">3</span><span class="strut"> </span></p> -<p id="t10" class="pln"> <span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t11" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> -<p id="t12" class="pln"><span class="strut"> </span></p> -<p id="t13" 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"> </span></p> +<p id="t1" 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"> </span></p> +<p id="t2" class="pln"> <span class="com"># This will be a branch that misses the else.</span><span class="strut"> </span></p> +<p id="t3" class="stm par run hide_run"><span class="annotate short">3 ↛ 6</span><span class="annotate long">line 3 didn't jump to line 6, because the condition on line 3 was never false</span> <span class="key">if</span> <span class="nam">x</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t4" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">3</span><span class="strut"> </span></p> +<p id="t5" class="pln"> <span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t6" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> +<p id="t7" class="pln"><span class="strut"> </span></p> +<p id="t8" 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"> </span></p> +<p id="t9" class="pln"><span class="strut"> </span></p> +<p id="t10" 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"> </span></p> +<p id="t11" class="pln"> <span class="com"># A missed else that branches to "exit"</span><span class="strut"> </span></p> +<p id="t12" class="stm par run hide_run"><span class="annotate short">12 ↛ exit</span><span class="annotate long">line 12 didn't return from function 'two', because the condition on line 12 was never false</span> <span class="key">if</span> <span class="nam">x</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t13" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">5</span><span class="strut"> </span></p> <p id="t14" class="pln"><span class="strut"> </span></p> -<p id="t15" 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"> </span></p> -<p id="t16" class="pln"> <span class="com"># A missed else that branches to "exit"</span><span class="strut"> </span></p> -<p id="t17" class="stm par run hide_run"><span class="annotate" title="no jump to this line number">exit</span> <span class="key">if</span> <span class="nam">x</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t18" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">5</span><span class="strut"> </span></p> -<p id="t19" class="pln"><span class="strut"> </span></p> -<p id="t20" 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"> </span></p> -<p id="t21" class="pln"><span class="strut"> </span></p> -<p id="t22" class="stm run hide_run"><span class="key">def</span> <span class="nam">three</span><span class="op">(</span><span class="op">)</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t23" class="stm run hide_run"> <span class="key">try</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t24" class="pln"> <span class="com"># This if has two branches, *neither* one taken.</span><span class="strut"> </span></p> -<p id="t25" class="stm par run hide_run"><span class="annotate" title="no jumps to these line numbers">26 28</span> <span class="key">if</span> <span class="nam">name_error_this_variable_doesnt_exist</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t26" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t27" class="pln"> <span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t28" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> -<p id="t29" class="stm run hide_run"> <span class="key">except</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t30" class="stm run hide_run"> <span class="key">pass</span><span class="strut"> </span></p> -<p id="t31" class="pln"><span class="strut"> </span></p> -<p id="t32" class="stm run hide_run"><span class="nam">three</span><span class="op">(</span><span class="op">)</span><span class="strut"> </span></p> - +<p id="t15" 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"> </span></p> +<p id="t16" class="pln"><span class="strut"> </span></p> +<p id="t17" class="stm run hide_run"><span class="key">def</span> <span class="nam">three</span><span class="op">(</span><span class="op">)</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t18" class="stm run hide_run"> <span class="key">try</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t19" class="pln"> <span class="com"># This if has two branches, *neither* one taken.</span><span class="strut"> </span></p> +<p id="t20" class="stm par run hide_run"><span class="annotate short">20 ↛ 21, 20 ↛ 23</span><span class="annotate long">2 missed branches: 1) line 20 didn't jump to line 21, because the condition on line 20 was never true, 2) line 20 didn't jump to line 23, because the condition on line 20 was never false</span> <span class="key">if</span> <span class="nam">name_error_this_variable_doesnt_exist</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t21" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> +<p id="t22" class="pln"> <span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t23" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> +<p id="t24" class="stm run hide_run"> <span class="key">except</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t25" class="stm run hide_run"> <span class="key">pass</span><span class="strut"> </span></p> +<p id="t26" class="pln"><span class="strut"> </span></p> +<p id="t27" class="stm run hide_run"><span class="nam">three</span><span class="op">(</span><span class="op">)</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_b_branch/index.html b/tests/farm/html/gold_b_branch/index.html index ebc3a106..a0346e86 100644 --- a/tests/farm/html/gold_b_branch/index.html +++ b/tests/farm/html/gold_b_branch/index.html @@ -4,9 +4,8 @@ <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -15,21 +14,17 @@ </script> </head> <body class="indexfile"> - <div id="header"> <div class="content"> <h1>Coverage report: <span class="pc_cov">70%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -39,74 +34,59 @@ <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> 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>17</td> <td>3</td> <td>0</td> - <td>6</td> <td>4</td> - <td class="right" data-ratio="16 23">70%</td> </tr> </tfoot> <tbody> - <tr class="file"> - <td class="name left"><a href="b.html">b</a></td> + <td class="name left"><a href="b_py.html">b.py</a></td> <td>17</td> <td>3</td> <td>0</td> - <td>6</td> <td>4</td> - <td class="right" data-ratio="16 23">70%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="http://nedbatchelder.com/code/coverage/4.0a1">coverage.py v4.0a1</a> + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_bom/2/bom_py.html b/tests/farm/html/gold_bom/2/bom_py.html new file mode 100644 index 00000000..14f25413 --- /dev/null +++ b/tests/farm/html/gold_bom/2/bom_py.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<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.py: 71%</title> + <link rel="stylesheet" href="style.css" type="text/css"> + <script type="text/javascript" src="jquery.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"> + jQuery(document).ready(coverage.pyfile_ready); + </script> +</head> +<body class="pyfile"> +<div id="header"> + <div class="content"> + <h1>Coverage for <b>bom.py</b> : + <span class="pc_cov">71%</span> + </h1> + <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> + <h2 class="stats"> + 7 statements + <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" alt="Hide keyboard shortcuts" /> + <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> toggle line displays + </p> + <p class="keyhelp"> + <span class="key">j</span> + <span class="key">k</span> next/prev highlighted chunk + </p> + <p class="keyhelp"> + <span class="key">0</span> (zero) top of page + </p> + <p class="keyhelp"> + <span class="key">1</span> (one) first highlighted chunk + </p> + </div> +</div> +<div id="source"> + <table> + <tr> + <td class="linenos"> +<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"> +<p id="t1" class="pln"><span class="com"># A Python source file in utf-8, with BOM.</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="nam">math</span> <span class="op">=</span> <span class="str">"3×4 = 12, ÷2 = 6±0"</span><span class="strut"> </span></p> +<p id="t3" class="pln"><span class="strut"> </span></p> +<p id="t4" class="stm run hide_run"><span class="key">import</span> <span class="nam">sys</span><span class="strut"> </span></p> +<p id="t5" class="pln"><span class="strut"> </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">>=</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"> </span></p> +<p id="t7" class="stm mis"> <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"> </span></p> +<p id="t8" class="stm mis"> <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">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span><span class="strut"> </span></p> +<p id="t9" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t10" 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">21</span><span class="strut"> </span></p> +<p id="t11" 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="nam">decode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span><span class="strut"> </span></p> + </td> + </tr> + </table> +</div> +<div id="footer"> + <div class="content"> + <p> + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-23 06:35 + </p> + </div> +</div> +</body> +</html> diff --git a/tests/farm/html/gold_bom/2/index.html b/tests/farm/html/gold_bom/2/index.html new file mode 100644 index 00000000..bde4bb46 --- /dev/null +++ b/tests/farm/html/gold_bom/2/index.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<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.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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"> + jQuery(document).ready(coverage.index_ready); + </script> +</head> +<body class="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" alt="Show keyboard shortcuts" /> + <form id="filter_container"> + <input id="filter" type="text" value="" placeholder="filter..." /> + </form> + </div> +</div> +<div class="help_panel"> + <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> + <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> 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" data-ratio="5 7">71%</td> + </tr> + </tfoot> + <tbody> + <tr class="file"> + <td class="name left"><a href="bom_py.html">bom.py</a></td> + <td>7</td> + <td>2</td> + <td>0</td> + <td class="right" data-ratio="5 7">71%</td> + </tr> + </tbody> + </table> + <p id="no_rows"> + No items found using the specified filter. + </p> +</div> +<div id="footer"> + <div class="content"> + <p> + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-23 06:35 + </p> + </div> +</div> +</body> +</html> diff --git a/tests/farm/html/gold_bom/bom_py.html b/tests/farm/html/gold_bom/bom_py.html index 127f2f45..78d7f7b7 100644 --- a/tests/farm/html/gold_bom/bom_py.html +++ b/tests/farm/html/gold_bom/bom_py.html @@ -2,12 +2,9 @@ <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> + <title>Coverage for bom.py: 71%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -17,49 +14,42 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> - <h1>Coverage for <b>bom</b> : + <h1>Coverage for <b>bom.py</b> : <span class="pc_cov">71%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 7 statements <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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> @@ -70,38 +60,35 @@ <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="n7" class="stm run hide_run"><a href="#n7">7</a></p> +<p id="n8" class="stm run hide_run"><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> - +<p id="n10" class="stm mis"><a href="#n10">10</a></p> +<p id="n11" class="stm mis"><a href="#n11">11</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="err">�</span><span class="err">�</span><span class="err">�</span><span class="com"># A python source file in utf-8, with BOM</span><span class="strut"> </span></p> -<p id="t2" class="stm run hide_run"><span class="nam">math</span> <span class="op">=</span> <span class="str">"3×4 = 12, ÷2 = 6±0"</span><span class="strut"> </span></p> +<p id="t1" class="pln"><span class="com"># A Python source file in utf-8, with BOM.</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="nam">math</span> <span class="op">=</span> <span class="str">"3×4 = 12, ÷2 = 6±0"</span><span class="strut"> </span></p> <p id="t3" class="pln"><span class="strut"> </span></p> <p id="t4" class="stm run hide_run"><span class="key">import</span> <span class="nam">sys</span><span class="strut"> </span></p> <p id="t5" class="pln"><span class="strut"> </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">>=</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"> </span></p> -<p id="t7" class="stm mis"> <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"> </span></p> -<p id="t8" class="stm mis"> <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">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span><span class="strut"> </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">>=</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"> </span></p> +<p id="t7" 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"> </span></p> +<p id="t8" 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="nam">encode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span><span class="strut"> </span></p> <p id="t9" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t10" 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">21</span><span class="strut"> </span></p> -<p id="t11" 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="nam">decode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span><span class="strut"> </span></p> - +<p id="t10" class="stm mis"> <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"> </span></p> +<p id="t11" class="stm mis"> <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">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="http://nedbatchelder.com/code/coverage/4.0a1">coverage.py v4.0a1</a> + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_bom/index.html b/tests/farm/html/gold_bom/index.html index 77696727..4c4d9897 100644 --- a/tests/farm/html/gold_bom/index.html +++ b/tests/farm/html/gold_bom/index.html @@ -4,9 +4,8 @@ <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -15,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -39,62 +34,51 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="5 7">71%</td> </tr> </tfoot> <tbody> - <tr class="file"> - <td class="name left"><a href="bom.html">bom</a></td> + <td class="name left"><a href="bom_py.html">bom.py</a></td> <td>7</td> <td>2</td> <td>0</td> - <td class="right" data-ratio="5 7">71%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="http://nedbatchelder.com/code/coverage/4.0a1">coverage.py v4.0a1</a> + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_isolatin1/index.html b/tests/farm/html/gold_isolatin1/index.html index ee49cc5e..c648ae7d 100644 --- a/tests/farm/html/gold_isolatin1/index.html +++ b/tests/farm/html/gold_isolatin1/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,63 +34,51 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="2 2">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="isolatin1_py.html">isolatin1.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_isolatin1/isolatin1_py.html b/tests/farm/html/gold_isolatin1/isolatin1_py.html index bad0d78d..e8ad244b 100644 --- a/tests/farm/html/gold_isolatin1/isolatin1_py.html +++ b/tests/farm/html/gold_isolatin1/isolatin1_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,49 +14,42 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>isolatin1.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> @@ -70,36 +57,26 @@ <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="pln"><a href="#n4">4</a></p> -<p id="n5" class="pln"><a href="#n5">5</a></p> -<p id="n6" class="pln"><a href="#n6">6</a></p> -<p id="n7" class="stm run hide_run"><a href="#n7">7</a></p> -<p id="n8" class="stm run hide_run"><a href="#n8">8</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"> <p id="t1" class="pln"><span class="com"># -*- coding: iso8859-1 -*-</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="com"># A Python source file in another encoding.</span><span class="strut"> </span></p> -<p id="t6" class="pln"><span class="strut"> </span></p> -<p id="t7" class="stm run hide_run"><span class="nam">math</span> <span class="op">=</span> <span class="str">"3×4 = 12, ÷2 = 6±0"</span><span class="strut"> </span></p> -<p id="t8" 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"> </span></p> - +<p id="t2" class="pln"><span class="com"># A Python source file in another encoding.</span><span class="strut"> </span></p> +<p id="t3" class="pln"><span class="strut"> </span></p> +<p id="t4" class="stm run hide_run"><span class="nam">math</span> <span class="op">=</span> <span class="str">"3×4 = 12, ÷2 = 6±0"</span><span class="strut"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_1/index.html b/tests/farm/html/gold_omit_1/index.html index 404d6b6a..289c6f10 100644 --- a/tests/farm/html/gold_omit_1/index.html +++ b/tests/farm/html/gold_omit_1/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,90 +34,72 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="14 14">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="m1_py.html">m1.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="m2_py.html">m2.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="m3_py.html">m3.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="main_py.html">main.py</a></td> <td>8</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="8 8">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_1/m1_py.html b/tests/farm/html/gold_omit_1/m1_py.html index 44d73e49..05b2bd49 100644 --- a/tests/farm/html/gold_omit_1/m1_py.html +++ b/tests/farm/html/gold_omit_1/m1_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m1.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m1a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m1b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_1/m2_py.html b/tests/farm/html/gold_omit_1/m2_py.html index ff75cbe9..056e7af1 100644 --- a/tests/farm/html/gold_omit_1/m2_py.html +++ b/tests/farm/html/gold_omit_1/m2_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m2.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m2a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m2b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_1/m3_py.html b/tests/farm/html/gold_omit_1/m3_py.html index 4cdcc088..428527b2 100644 --- a/tests/farm/html/gold_omit_1/m3_py.html +++ b/tests/farm/html/gold_omit_1/m3_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m3.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m3a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m3b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_1/main_py.html b/tests/farm/html/gold_omit_1/main_py.html index d33dfedc..3fbc4af7 100644 --- a/tests/farm/html/gold_omit_1/main_py.html +++ b/tests/farm/html/gold_omit_1/main_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,96 +14,79 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>main.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 8 statements <span class="run hide_run shortkey_r button_toggle_run">8 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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="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="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="stm run hide_run"><a href="#n13">13</a></p> - +<p id="n10" class="stm run hide_run"><a href="#n10">10</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t1" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> +<p id="t3" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t4" class="pln"><span class="strut"> </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"> </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"> </span></p> <p id="t7" class="pln"><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="nam">a</span> <span class="op">=</span> <span class="num">5</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"><span class="nam">b</span> <span class="op">=</span> <span class="num">6</span><span class="strut"> </span></p> -<p id="t10" class="pln"><span class="strut"> </span></p> -<p id="t11" 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"> </span></p> -<p id="t12" 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"> </span></p> -<p id="t13" 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"> </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"> </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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_2/index.html b/tests/farm/html/gold_omit_2/index.html index 01c13e31..5813c0dc 100644 --- a/tests/farm/html/gold_omit_2/index.html +++ b/tests/farm/html/gold_omit_2/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,81 +34,65 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="12 12">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="m2_py.html">m2.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="m3_py.html">m3.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="main_py.html">main.py</a></td> <td>8</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="8 8">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_2/m2_py.html b/tests/farm/html/gold_omit_2/m2_py.html index ff75cbe9..056e7af1 100644 --- a/tests/farm/html/gold_omit_2/m2_py.html +++ b/tests/farm/html/gold_omit_2/m2_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m2.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m2a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m2b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_2/m3_py.html b/tests/farm/html/gold_omit_2/m3_py.html index 4cdcc088..428527b2 100644 --- a/tests/farm/html/gold_omit_2/m3_py.html +++ b/tests/farm/html/gold_omit_2/m3_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m3.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m3a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m3b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_2/main_py.html b/tests/farm/html/gold_omit_2/main_py.html index d33dfedc..3fbc4af7 100644 --- a/tests/farm/html/gold_omit_2/main_py.html +++ b/tests/farm/html/gold_omit_2/main_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,96 +14,79 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>main.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 8 statements <span class="run hide_run shortkey_r button_toggle_run">8 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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="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="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="stm run hide_run"><a href="#n13">13</a></p> - +<p id="n10" class="stm run hide_run"><a href="#n10">10</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t1" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> +<p id="t3" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t4" class="pln"><span class="strut"> </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"> </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"> </span></p> <p id="t7" class="pln"><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="nam">a</span> <span class="op">=</span> <span class="num">5</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"><span class="nam">b</span> <span class="op">=</span> <span class="num">6</span><span class="strut"> </span></p> -<p id="t10" class="pln"><span class="strut"> </span></p> -<p id="t11" 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"> </span></p> -<p id="t12" 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"> </span></p> -<p id="t13" 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"> </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"> </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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_3/index.html b/tests/farm/html/gold_omit_3/index.html index 79192911..4ebcf4a4 100644 --- a/tests/farm/html/gold_omit_3/index.html +++ b/tests/farm/html/gold_omit_3/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,72 +34,58 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="10 10">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="m3_py.html">m3.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="main_py.html">main.py</a></td> <td>8</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="8 8">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_3/m3_py.html b/tests/farm/html/gold_omit_3/m3_py.html index 4cdcc088..428527b2 100644 --- a/tests/farm/html/gold_omit_3/m3_py.html +++ b/tests/farm/html/gold_omit_3/m3_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m3.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m3a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m3b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_3/main_py.html b/tests/farm/html/gold_omit_3/main_py.html index d33dfedc..3fbc4af7 100644 --- a/tests/farm/html/gold_omit_3/main_py.html +++ b/tests/farm/html/gold_omit_3/main_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,96 +14,79 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>main.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 8 statements <span class="run hide_run shortkey_r button_toggle_run">8 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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="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="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="stm run hide_run"><a href="#n13">13</a></p> - +<p id="n10" class="stm run hide_run"><a href="#n10">10</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t1" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> +<p id="t3" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t4" class="pln"><span class="strut"> </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"> </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"> </span></p> <p id="t7" class="pln"><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="nam">a</span> <span class="op">=</span> <span class="num">5</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"><span class="nam">b</span> <span class="op">=</span> <span class="num">6</span><span class="strut"> </span></p> -<p id="t10" class="pln"><span class="strut"> </span></p> -<p id="t11" 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"> </span></p> -<p id="t12" 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"> </span></p> -<p id="t13" 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"> </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"> </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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_4/index.html b/tests/farm/html/gold_omit_4/index.html index 0e52f497..e7588714 100644 --- a/tests/farm/html/gold_omit_4/index.html +++ b/tests/farm/html/gold_omit_4/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,81 +34,65 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="12 12">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="m1_py.html">m1.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="m3_py.html">m3.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="main_py.html">main.py</a></td> <td>8</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="8 8">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_4/m1_py.html b/tests/farm/html/gold_omit_4/m1_py.html index 44d73e49..05b2bd49 100644 --- a/tests/farm/html/gold_omit_4/m1_py.html +++ b/tests/farm/html/gold_omit_4/m1_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m1.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m1a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m1b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_4/m3_py.html b/tests/farm/html/gold_omit_4/m3_py.html index 4cdcc088..428527b2 100644 --- a/tests/farm/html/gold_omit_4/m3_py.html +++ b/tests/farm/html/gold_omit_4/m3_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m3.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m3a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m3b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_4/main_py.html b/tests/farm/html/gold_omit_4/main_py.html index d33dfedc..3fbc4af7 100644 --- a/tests/farm/html/gold_omit_4/main_py.html +++ b/tests/farm/html/gold_omit_4/main_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,96 +14,79 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>main.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 8 statements <span class="run hide_run shortkey_r button_toggle_run">8 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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="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="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="stm run hide_run"><a href="#n13">13</a></p> - +<p id="n10" class="stm run hide_run"><a href="#n10">10</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t1" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> +<p id="t3" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t4" class="pln"><span class="strut"> </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"> </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"> </span></p> <p id="t7" class="pln"><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="nam">a</span> <span class="op">=</span> <span class="num">5</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"><span class="nam">b</span> <span class="op">=</span> <span class="num">6</span><span class="strut"> </span></p> -<p id="t10" class="pln"><span class="strut"> </span></p> -<p id="t11" 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"> </span></p> -<p id="t12" 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"> </span></p> -<p id="t13" 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"> </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"> </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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_5/index.html b/tests/farm/html/gold_omit_5/index.html index 509e7900..e2c1a132 100644 --- a/tests/farm/html/gold_omit_5/index.html +++ b/tests/farm/html/gold_omit_5/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,72 +34,58 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="10 10">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="m1_py.html">m1.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="main_py.html">main.py</a></td> <td>8</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="8 8">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_5/m1_py.html b/tests/farm/html/gold_omit_5/m1_py.html index 44d73e49..05b2bd49 100644 --- a/tests/farm/html/gold_omit_5/m1_py.html +++ b/tests/farm/html/gold_omit_5/m1_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,80 +14,63 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>m1.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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> - +<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"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="nam">m1a</span> <span class="op">=</span> <span class="num">1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="nam">m1b</span> <span class="op">=</span> <span class="num">2</span><span class="strut"> </span></p> - +<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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_omit_5/main_py.html b/tests/farm/html/gold_omit_5/main_py.html index d33dfedc..3fbc4af7 100644 --- a/tests/farm/html/gold_omit_5/main_py.html +++ b/tests/farm/html/gold_omit_5/main_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,96 +14,79 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>main.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 8 statements <span class="run hide_run shortkey_r button_toggle_run">8 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<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="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="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="stm run hide_run"><a href="#n13">13</a></p> - +<p id="n10" class="stm run hide_run"><a href="#n10">10</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> -<p id="t5" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t1" class="stm run hide_run"><span class="key">import</span> <span class="nam">m1</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="key">import</span> <span class="nam">m2</span><span class="strut"> </span></p> +<p id="t3" class="stm run hide_run"><span class="key">import</span> <span class="nam">m3</span><span class="strut"> </span></p> +<p id="t4" class="pln"><span class="strut"> </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"> </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"> </span></p> <p id="t7" class="pln"><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="nam">a</span> <span class="op">=</span> <span class="num">5</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"><span class="nam">b</span> <span class="op">=</span> <span class="num">6</span><span class="strut"> </span></p> -<p id="t10" class="pln"><span class="strut"> </span></p> -<p id="t11" 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"> </span></p> -<p id="t12" 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"> </span></p> -<p id="t13" 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"> </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"> </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"> </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"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_other/blah_blah_other_py.html b/tests/farm/html/gold_other/blah_blah_other_py.html index 54861c23..36e3653d 100644 --- a/tests/farm/html/gold_other/blah_blah_other_py.html +++ b/tests/farm/html/gold_other/blah_blah_other_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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 /Users/ned/coverage/trunk/tests/farm/html/othersrc/other.py: 100%</title> + <title>Coverage for /private/var/folders/j2/gr3cj3jn63s5q8g3bjvw57hm0000gp/T/coverage_test/tests_test_html_HtmlGoldTests_test_other_95946649/othersrc/other.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,49 +14,42 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> - <h1>Coverage for <b>/Users/ned/coverage/trunk/tests/farm/html/othersrc/other.py</b> : + <h1>Coverage for <b>/private/var/folders/j2/gr3cj3jn63s5q8g3bjvw57hm0000gp/T/coverage_test/tests_test_html_HtmlGoldTests_test_other_95946649/othersrc/other.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 1 statements <span class="run hide_run shortkey_r button_toggle_run">1 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> @@ -70,34 +57,24 @@ <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="pln"><a href="#n4">4</a></p> -<p id="n5" class="pln"><a href="#n5">5</a></p> -<p id="n6" class="pln"><a href="#n6">6</a></p> -<p id="n7" class="stm run hide_run"><a href="#n7">7</a></p> - +<p id="n4" class="stm run hide_run"><a href="#n4">4</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> +<p id="t1" class="pln"><span class="com"># A file in another directory. We're checking that it ends up in the</span><span class="strut"> </span></p> +<p id="t2" class="pln"><span class="com"># HTML report.</span><span class="strut"> </span></p> <p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="com"># A file in another directory. We're checking that it ends up in the</span><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="com"># HTML report.</span><span class="strut"> </span></p> -<p id="t6" class="pln"><span class="strut"> </span></p> -<p id="t7" class="stm run hide_run"><span class="key">print</span><span class="op">(</span><span class="str">"This is the other src!"</span><span class="op">)</span><span class="strut"> </span></p> - +<p id="t4" class="stm run hide_run"><span class="nam">print</span><span class="op">(</span><span class="str">"This is the other src!"</span><span class="op">)</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_other/here_py.html b/tests/farm/html/gold_other/here_py.html index 82aa70a2..8efa027a 100644 --- a/tests/farm/html/gold_other/here_py.html +++ b/tests/farm/html/gold_other/here_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 75%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,92 +14,71 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>here.py</b> : <span class="pc_cov">75%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 4 statements <span class="run hide_run shortkey_r button_toggle_run">3 run</span> <span class="mis shortkey_m button_toggle_mis">1 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<p id="n1" class="pln"><a href="#n1">1</a></p> +<p id="n1" class="stm run hide_run"><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="pln"><a href="#n4">4</a></p> +<p id="n3" class="stm run hide_run"><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="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 mis"><a href="#n11">11</a></p> - +<p id="n6" class="stm mis"><a href="#n6">6</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="com"># A test file for HTML reporting by coverage.py.</span><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">import</span> <span class="nam">other</span><span class="strut"> </span></p> -<p id="t7" class="pln"><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="key">if</span> <span class="num">1</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"> <span class="nam">h</span> <span class="op">=</span> <span class="num">3</span><span class="strut"> </span></p> -<p id="t10" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t11" class="stm mis"> <span class="nam">h</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> - +<p id="t1" class="stm run hide_run"><span class="key">import</span> <span class="nam">other</span><span class="strut"> </span></p> +<p id="t2" class="pln"><span class="strut"> </span></p> +<p id="t3" class="stm run hide_run"><span class="key">if</span> <span class="num">1</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t4" class="stm run hide_run"> <span class="nam">h</span> <span class="op">=</span> <span class="num">3</span><span class="strut"> </span></p> +<p id="t5" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t6" class="stm mis"> <span class="nam">h</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_other/index.html b/tests/farm/html/gold_other/index.html index 9a8d72de..10d4ae9a 100644 --- a/tests/farm/html/gold_other/index.html +++ b/tests/farm/html/gold_other/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,72 +34,58 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="4 5">80%</td> </tr> </tfoot> <tbody> - <tr class="file"> - <td class="name left"><a href="_Users_ned_coverage_trunk_tests_farm_html_othersrc_other_py.html">/Users/ned/coverage/trunk/tests/farm/html/othersrc/other.py</a></td> + <td class="name left"><a href="_private_var_folders_j2_gr3cj3jn63s5q8g3bjvw57hm0000gp_T_coverage_test_tests_test_html_HtmlGoldTests_test_other_95946649_othersrc_other_py.html">/private/var/folders/j2/gr3cj3jn63s5q8g3bjvw57hm0000gp/T/coverage_test/tests_test_html_HtmlGoldTests_test_other_95946649/othersrc/other.py</a></td> <td>1</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="1 1">100%</td> </tr> - <tr class="file"> <td class="name left"><a href="here_py.html">here.py</a></td> <td>4</td> <td>1</td> <td>0</td> - <td class="right" data-ratio="3 4">75%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_partial/index.html b/tests/farm/html/gold_partial/index.html index 40ffcc4c..1948615c 100644 --- a/tests/farm/html/gold_partial/index.html +++ b/tests/farm/html/gold_partial/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,75 +34,59 @@ <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> 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>9</td> <td>0</td> + <td>1</td> + <td>2</td> <td>0</td> - - <td>4</td> - <td>0</td> - - <td class="right" data-ratio="12 12">100%</td> + <td class="right" data-ratio="11 11">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="partial_py.html">partial.py</a></td> - <td>8</td> - <td>0</td> + <td>9</td> <td>0</td> - - <td>4</td> + <td>1</td> + <td>2</td> <td>0</td> - - <td class="right" data-ratio="12 12">100%</td> + <td class="right" data-ratio="11 11">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_partial/partial_py.html b/tests/farm/html/gold_partial/partial_py.html index 796153f1..44238f68 100644 --- a/tests/farm/html/gold_partial/partial_py.html +++ b/tests/farm/html/gold_partial/partial_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,114 +14,100 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>partial.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> - 8 statements - <span class="run hide_run shortkey_r button_toggle_run">8 run</span> + 9 statements + <span class="run hide_run shortkey_r button_toggle_run">9 run</span> <span class="mis shortkey_m button_toggle_mis">0 missing</span> - <span class="exc shortkey_x button_toggle_exc">0 excluded</span> - - + <span class="exc shortkey_x button_toggle_exc">1 excluded</span> <span class="par run hide_run shortkey_p button_toggle_par">0 partial</span> - </h2> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> <p id="n1" class="pln"><a href="#n1">1</a></p> -<p id="n2" class="pln"><a href="#n2">2</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="pln"><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="pln"><a href="#n7">7</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> +<p id="n6" class="pln"><a href="#n6">6</a></p> +<p id="n7" class="stm run hide_run"><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="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> -<p id="n12" class="stm run hide_run"><a href="#n12">12</a></p> +<p id="n12" class="pln"><a href="#n12">12</a></p> <p id="n13" class="pln"><a href="#n13">13</a></p> -<p id="n14" class="stm 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="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="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="pln"><a href="#n19">19</a></p> -<p id="n20" class="pln"><a href="#n20">20</a></p> -<p id="n21" class="stm run hide_run"><a href="#n21">21</a></p> - +<p id="n19" class="stm run hide_run"><a href="#n19">19</a></p> +<p id="n20" class="exc"><a href="#n20">20</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> +<p id="t1" class="pln"><span class="com"># partial branches and excluded lines</span><span class="strut"> </span></p> +<p id="t2" class="stm run hide_run"><span class="nam">a</span> <span class="op">=</span> <span class="num">6</span><span class="strut"> </span></p> <p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="com"># partial branches</span><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="nam">a</span> <span class="op">=</span> <span class="num">3</span><span class="strut"> </span></p> -<p id="t7" class="pln"><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="key">while</span> <span class="nam">True</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t9" class="stm run hide_run"> <span class="key">break</span><span class="strut"> </span></p> -<p id="t10" class="pln"><span class="strut"> </span></p> -<p id="t11" class="stm run hide_run"><span class="key">while</span> <span class="num">1</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t12" class="stm run hide_run"> <span class="key">break</span><span class="strut"> </span></p> -<p id="t13" class="pln"><span class="strut"> </span></p> -<p id="t14" class="stm run hide_run"><span class="key">while</span> <span class="nam">a</span><span class="op">:</span> <span class="com"># pragma: no branch</span><span class="strut"> </span></p> -<p id="t15" class="stm run hide_run"> <span class="key">break</span><span class="strut"> </span></p> -<p id="t16" class="pln"><span class="strut"> </span></p> -<p id="t17" class="pln"><span class="key">if</span> <span class="num">0</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t18" class="pln"> <span class="nam">never_happen</span><span class="op">(</span><span class="op">)</span><span class="strut"> </span></p> -<p id="t19" class="pln"><span class="strut"> </span></p> -<p id="t20" class="pln"><span class="key">if</span> <span class="num">1</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t21" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">13</span><span class="strut"> </span></p> - +<p id="t4" class="stm run hide_run"><span class="key">while</span> <span class="key">True</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t5" class="stm run hide_run"> <span class="key">break</span><span class="strut"> </span></p> +<p id="t6" class="pln"><span class="strut"> </span></p> +<p id="t7" class="stm run hide_run"><span class="key">while</span> <span class="num">1</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t8" class="stm run hide_run"> <span class="key">break</span><span class="strut"> </span></p> +<p id="t9" class="pln"><span class="strut"> </span></p> +<p id="t10" class="stm run hide_run"><span class="key">while</span> <span class="nam">a</span><span class="op">:</span> <span class="com"># pragma: no branch</span><span class="strut"> </span></p> +<p id="t11" class="stm run hide_run"> <span class="key">break</span><span class="strut"> </span></p> +<p id="t12" class="pln"><span class="strut"> </span></p> +<p id="t13" class="pln"><span class="key">if</span> <span class="num">0</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t14" class="pln"> <span class="nam">never_happen</span><span class="op">(</span><span class="op">)</span><span class="strut"> </span></p> +<p id="t15" class="pln"><span class="strut"> </span></p> +<p id="t16" class="pln"><span class="key">if</span> <span class="num">1</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t17" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">21</span><span class="strut"> </span></p> +<p id="t18" class="pln"><span class="strut"> </span></p> +<p id="t19" class="stm run hide_run"><span class="key">if</span> <span class="nam">a</span> <span class="op">==</span> <span class="num">23</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t20" class="exc"> <span class="key">raise</span> <span class="nam">AssertionError</span><span class="op">(</span><span class="str">"Can't"</span><span class="op">)</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_styled/a_py.html b/tests/farm/html/gold_styled/a_py.html index c8221753..dd569b1b 100644 --- a/tests/farm/html/gold_styled/a_py.html +++ b/tests/farm/html/gold_styled/a_py.html @@ -1,18 +1,11 @@ - - - <!DOCTYPE html> <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.py: 67%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <link rel="stylesheet" href="extra.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -22,90 +15,69 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>a.py</b> : <span class="pc_cov">67%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 3 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">1 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> <td class="linenos"> -<p id="n1" class="pln"><a href="#n1">1</a></p> +<p id="n1" class="stm run hide_run"><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="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="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="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="pln"><a href="#n9">9</a></p> -<p id="n10" class="stm mis"><a href="#n10">10</a></p> - +<p id="n5" class="stm mis"><a href="#n5">5</a></p> </td> <td class="text"> -<p id="t1" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="com"># A test file for HTML reporting by coverage.py.</span><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="strut"> </span></p> -<p id="t6" class="stm run hide_run"><span class="key">if</span> <span class="num">1</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t7" class="pln"> <span class="com"># Needed a < to look at HTML entities.</span><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"> <span class="nam">a</span> <span class="op">=</span> <span class="num">3</span><span class="strut"> </span></p> -<p id="t9" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> -<p id="t10" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> - +<p id="t1" class="stm run hide_run"><span class="key">if</span> <span class="num">1</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t2" class="pln"> <span class="com"># Needed a < to look at HTML entities.</span><span class="strut"> </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"> </span></p> +<p id="t4" class="pln"><span class="key">else</span><span class="op">:</span><span class="strut"> </span></p> +<p id="t5" class="stm mis"> <span class="nam">a</span> <span class="op">=</span> <span class="num">4</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_styled/index.html b/tests/farm/html/gold_styled/index.html index 05a8fe21..1f86b772 100644 --- a/tests/farm/html/gold_styled/index.html +++ b/tests/farm/html/gold_styled/index.html @@ -1,17 +1,12 @@ - - - <!DOCTYPE html> <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"> - <link rel="stylesheet" href="extra.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -20,21 +15,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -44,63 +35,51 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="2 3">67%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="a_py.html">a.py</a></td> <td>3</td> <td>1</td> <td>0</td> - <td class="right" data-ratio="2 3">67%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_styled/style.css b/tests/farm/html/gold_styled/style.css index c3767eef..0cd0cce1 100644 --- a/tests/farm/html/gold_styled/style.css +++ b/tests/farm/html/gold_styled/style.css @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ /* CSS styles for coverage.py. */ /* Page-wide styles */ diff --git a/tests/farm/html/gold_unicode/index.html b/tests/farm/html/gold_unicode/index.html index 6d8268d8..35a98a9e 100644 --- a/tests/farm/html/gold_unicode/index.html +++ b/tests/farm/html/gold_unicode/index.html @@ -1,15 +1,11 @@ - - - <!DOCTYPE html> <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.min.js"></script> - <script type="text/javascript" src="jquery.debounce.min.js"></script> + <script type="text/javascript" src="jquery.ba-throttle-debounce.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> @@ -18,21 +14,17 @@ </script> </head> <body class="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" alt="Show keyboard shortcuts" /> - <form id="filter_container"> <input id="filter" type="text" value="" placeholder="filter..." /> </form> </div> </div> - <div class="help_panel"> <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> <p class="legend">Hot-keys on this page</p> @@ -42,63 +34,51 @@ <span class="key">s</span> <span class="key">m</span> <span class="key">x</span> - <span class="key">c</span> 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" data-ratio="2 2">100%</td> </tr> </tfoot> <tbody> - <tr class="file"> <td class="name left"><a href="unicode_py.html">unicode.py</a></td> <td>2</td> <td>0</td> <td>0</td> - <td class="right" data-ratio="2 2">100%</td> </tr> - </tbody> </table> - <p id="no_rows"> No items found using the specified filter. </p> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:31 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_unicode/unicode_py.html b/tests/farm/html/gold_unicode/unicode_py.html index 83b0f382..174a9a27 100644 --- a/tests/farm/html/gold_unicode/unicode_py.html +++ b/tests/farm/html/gold_unicode/unicode_py.html @@ -1,16 +1,10 @@ - - - <!DOCTYPE html> <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.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> <script type="text/javascript" src="jquery.isonscreen.js"></script> @@ -20,49 +14,42 @@ </script> </head> <body class="pyfile"> - <div id="header"> <div class="content"> <h1>Coverage for <b>unicode.py</b> : <span class="pc_cov">100%</span> </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> 2 statements <span class="run hide_run shortkey_r button_toggle_run">2 run</span> <span class="mis shortkey_m button_toggle_mis">0 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" alt="Hide keyboard shortcuts" /> -<p class="legend">Hot-keys on this page</p> + <p class="legend">Hot-keys on this page</p> <div> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">r</span> <span class="key">m</span> <span class="key">x</span> <span class="key">p</span> toggle line displays </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">j</span> <span class="key">k</span> next/prev highlighted chunk </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">0</span> (zero) top of page </p> -<p class="keyhelp"> + <p class="keyhelp"> <span class="key">1</span> (one) first highlighted chunk </p> </div> </div> - <div id="source"> <table> <tr> @@ -70,36 +57,26 @@ <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="pln"><a href="#n4">4</a></p> -<p id="n5" class="pln"><a href="#n5">5</a></p> -<p id="n6" class="pln"><a href="#n6">6</a></p> -<p id="n7" class="stm run hide_run"><a href="#n7">7</a></p> -<p id="n8" class="stm run hide_run"><a href="#n8">8</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"> <p id="t1" class="pln"><span class="com"># -*- coding: utf-8 -*-</span><span class="strut"> </span></p> -<p id="t2" class="pln"><span class="com"># Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0</span><span class="strut"> </span></p> -<p id="t3" class="pln"><span class="com"># For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt</span><span class="strut"> </span></p> -<p id="t4" class="pln"><span class="strut"> </span></p> -<p id="t5" class="pln"><span class="com"># A Python source file with exotic characters.</span><span class="strut"> </span></p> -<p id="t6" class="pln"><span class="strut"> </span></p> -<p id="t7" class="stm run hide_run"><span class="nam">upside_down</span> <span class="op">=</span> <span class="str">"ʎd˙ǝbɐɹǝʌoɔ"</span><span class="strut"> </span></p> -<p id="t8" class="stm run hide_run"><span class="nam">surrogate</span> <span class="op">=</span> <span class="str">"db40,dd00: x󠄀"</span><span class="strut"> </span></p> - +<p id="t2" class="pln"><span class="com"># A Python source file with exotic characters.</span><span class="strut"> </span></p> +<p id="t3" class="pln"><span class="strut"> </span></p> +<p id="t4" class="stm run hide_run"><span class="nam">upside_down</span> <span class="op">=</span> <span class="str">"ʎd˙ǝbɐɹǝʌoɔ"</span><span class="strut"> </span></p> +<p id="t5" class="stm run hide_run"><span class="nam">surrogate</span> <span class="op">=</span> <span class="str">"db40,dd00: x󠄀"</span><span class="strut"> </span></p> </td> </tr> </table> </div> - <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-4.0a7">coverage.py v4.0a7</a>, - created at 2015-07-24 09:04 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a2">coverage.py v5.0a2</a>, + created at 2018-08-22 20:12 </p> </div> </div> - </body> </html> diff --git a/tests/farm/html/gold_x_xml/coverage.xml b/tests/farm/html/gold_x_xml/coverage.xml index 162824a0..1030f9f8 100644 --- a/tests/farm/html/gold_x_xml/coverage.xml +++ b/tests/farm/html/gold_x_xml/coverage.xml @@ -1,9 +1,9 @@ <?xml version="1.0" ?> -<coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.6667" lines-covered="2" lines-valid="3" timestamp="1437745880639" version="4.0a7"> - <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/coverage-4.0a7 --> +<coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.6667" lines-covered="2" lines-valid="3" timestamp="1534849155281" version="5.0a2"> + <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/coverage-5.0a2 --> <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd --> <sources> - <source>/Users/ned/coverage/trunk/tests/farm/html/src</source> + <source>/private/var/folders/j2/gr3cj3jn63s5q8g3bjvw57hm0000gp/T/coverage_test/tests_test_xml_XmlGoldTest_test_a_xml_1_43316963</source> </sources> <packages> <package branch-rate="0" complexity="0" line-rate="0.6667" name="."> @@ -11,9 +11,9 @@ <class branch-rate="0" complexity="0" filename="a.py" line-rate="0.6667" name="a.py"> <methods/> <lines> - <line hits="1" number="6"/> - <line hits="1" number="8"/> - <line hits="0" number="10"/> + <line hits="1" number="1"/> + <line hits="1" number="3"/> + <line hits="0" number="5"/> </lines> </class> </classes> diff --git a/tests/farm/html/gold_y_xml_branch/coverage.xml b/tests/farm/html/gold_y_xml_branch/coverage.xml index bcf1137b..71e08bb0 100644 --- a/tests/farm/html/gold_y_xml_branch/coverage.xml +++ b/tests/farm/html/gold_y_xml_branch/coverage.xml @@ -1,9 +1,9 @@ <?xml version="1.0" ?> -<coverage branch-rate="0.5" branches-covered="1" branches-valid="2" complexity="0" line-rate="0.8" lines-covered="4" lines-valid="5" timestamp="1437745880882" version="4.0a7"> - <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/coverage-4.0a7 --> +<coverage branch-rate="0.5" branches-covered="1" branches-valid="2" complexity="0" line-rate="0.8" lines-covered="4" lines-valid="5" timestamp="1534849423448" version="5.0a2"> + <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/coverage-5.0a2 --> <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd --> <sources> - <source>/Users/ned/coverage/trunk/tests/farm/html/src</source> + <source>/private/var/folders/j2/gr3cj3jn63s5q8g3bjvw57hm0000gp/T/coverage_test/tests_test_xml_XmlGoldTest_test_y_xml_branch_93378757</source> </sources> <packages> <package branch-rate="0.5" complexity="0" line-rate="0.8" name="."> @@ -11,11 +11,11 @@ <class branch-rate="0.5" complexity="0" filename="y.py" line-rate="0.8" name="y.py"> <methods/> <lines> - <line hits="1" number="6"/> - <line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="10" number="7"/> - <line hits="1" number="8"/> - <line hits="0" number="10"/> - <line hits="1" number="12"/> + <line hits="1" number="1"/> + <line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="5" number="2"/> + <line hits="1" number="3"/> + <line hits="0" number="5"/> + <line hits="1" number="7"/> </lines> </class> </classes> diff --git a/tests/farm/html/othersrc/other.py b/tests/farm/html/othersrc/other.py deleted file mode 100644 index bf0304d2..00000000 --- a/tests/farm/html/othersrc/other.py +++ /dev/null @@ -1,7 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A file in another directory. We're checking that it ends up in the -# HTML report. - -print("This is the other src!") diff --git a/tests/farm/html/src/a.py b/tests/farm/html/src/a.py deleted file mode 100644 index 85764e21..00000000 --- a/tests/farm/html/src/a.py +++ /dev/null @@ -1,10 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A test file for HTML reporting by coverage.py. - -if 1 < 2: - # Needed a < to look at HTML entities. - a = 3 -else: - a = 4 diff --git a/tests/farm/html/src/b.py b/tests/farm/html/src/b.py deleted file mode 100644 index cb673c22..00000000 --- a/tests/farm/html/src/b.py +++ /dev/null @@ -1,32 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A test file for HTML reporting by coverage.py. - -def one(x): - # This will be a branch that misses the else. - if x < 2: - a = 3 - else: - a = 4 - -one(1) - -def two(x): - # A missed else that branches to "exit" - if x: - a = 5 - -two(1) - -def three(): - try: - # This if has two branches, *neither* one taken. - if name_error_this_variable_doesnt_exist: - a = 1 - else: - a = 2 - except: - pass - -three() diff --git a/tests/farm/html/src/bom.py b/tests/farm/html/src/bom.py deleted file mode 100644 index 21d26ca2..00000000 --- a/tests/farm/html/src/bom.py +++ /dev/null @@ -1,14 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A Python source file in utf-8, with BOM. -math = "3×4 = 12, ÷2 = 6±0" - -import sys - -if sys.version_info >= (3, 0): - assert len(math) == 18 - assert len(math.encode('utf-8')) == 21 -else: - assert len(math) == 21 - assert len(math.decode('utf-8')) == 18 diff --git a/tests/farm/html/src/extra.css b/tests/farm/html/src/extra.css deleted file mode 100644 index 46c41fcd..00000000 --- a/tests/farm/html/src/extra.css +++ /dev/null @@ -1 +0,0 @@ -/* Doesn't matter what goes in here, it gets copied. */ diff --git a/tests/farm/html/src/here.py b/tests/farm/html/src/here.py deleted file mode 100644 index fee9960d..00000000 --- a/tests/farm/html/src/here.py +++ /dev/null @@ -1,11 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A test file for HTML reporting by coverage.py. - -import other - -if 1 < 2: - h = 3 -else: - h = 4 diff --git a/tests/farm/html/src/isolatin1.py b/tests/farm/html/src/isolatin1.py deleted file mode 100644 index 55a6f7de..00000000 --- a/tests/farm/html/src/isolatin1.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: iso8859-1 -*- -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A Python source file in another encoding. - -math = "34 = 12, 2 = 60" -assert len(math) == 18 diff --git a/tests/farm/html/src/m1.py b/tests/farm/html/src/m1.py deleted file mode 100644 index 524fb0aa..00000000 --- a/tests/farm/html/src/m1.py +++ /dev/null @@ -1,5 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -m1a = 1 -m1b = 2 diff --git a/tests/farm/html/src/m2.py b/tests/farm/html/src/m2.py deleted file mode 100644 index 2d13bfe2..00000000 --- a/tests/farm/html/src/m2.py +++ /dev/null @@ -1,5 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -m2a = 1 -m2b = 2 diff --git a/tests/farm/html/src/m3.py b/tests/farm/html/src/m3.py deleted file mode 100644 index 96e8b992..00000000 --- a/tests/farm/html/src/m3.py +++ /dev/null @@ -1,5 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -m3a = 1 -m3b = 2 diff --git a/tests/farm/html/src/main.py b/tests/farm/html/src/main.py deleted file mode 100644 index 238d0b58..00000000 --- a/tests/farm/html/src/main.py +++ /dev/null @@ -1,13 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -import m1 -import m2 -import m3 - -a = 5 -b = 6 - -assert m1.m1a == 1 -assert m2.m2a == 1 -assert m3.m3a == 1 diff --git a/tests/farm/html/src/omit4.ini b/tests/farm/html/src/omit4.ini deleted file mode 100644 index b792e703..00000000 --- a/tests/farm/html/src/omit4.ini +++ /dev/null @@ -1,5 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -[report] -omit = m2.py diff --git a/tests/farm/html/src/omit5.ini b/tests/farm/html/src/omit5.ini deleted file mode 100644 index 3b6add29..00000000 --- a/tests/farm/html/src/omit5.ini +++ /dev/null @@ -1,11 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -[report] -omit = - fooey - gooey, m[23]*, kablooey - helloworld - -[html] -directory = ../out/omit_5 diff --git a/tests/farm/html/src/partial.ini b/tests/farm/html/src/partial.ini deleted file mode 100644 index cdb241b5..00000000 --- a/tests/farm/html/src/partial.ini +++ /dev/null @@ -1,9 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -[run] -branch = True - -[report] -exclude_lines = - raise AssertionError diff --git a/tests/farm/html/src/partial.py b/tests/farm/html/src/partial.py deleted file mode 100644 index 0f8fbe3c..00000000 --- a/tests/farm/html/src/partial.py +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# partial branches and excluded lines - -a = 6 - -while True: - break - -while 1: - break - -while a: # pragma: no branch - break - -if 0: - never_happen() - -if 1: - a = 21 - -if a == 23: - raise AssertionError("Can't") diff --git a/tests/farm/html/src/run_a_xml_2.ini b/tests/farm/html/src/run_a_xml_2.ini deleted file mode 100644 index f632bd09..00000000 --- a/tests/farm/html/src/run_a_xml_2.ini +++ /dev/null @@ -1,6 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# Put all the XML output in xml_2 -[xml] -output = ../out/xml_2/coverage.xml diff --git a/tests/farm/html/src/tabbed.py b/tests/farm/html/src/tabbed.py deleted file mode 100644 index e897e9fa..00000000 --- a/tests/farm/html/src/tabbed.py +++ /dev/null @@ -1,10 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# This file should have tabs. -x = 1 -if x: - a = "Tabbed" # Aligned comments - if x: # look nice - b = "No spaces" # when they - c = "Done" # line up. diff --git a/tests/farm/html/src/unicode.py b/tests/farm/html/src/unicode.py deleted file mode 100644 index 37c5533a..00000000 --- a/tests/farm/html/src/unicode.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A Python source file with exotic characters. - -upside_down = "ʎd˙ǝbɐɹǝʌoɔ" -surrogate = "db40,dd00: x󠄀" diff --git a/tests/farm/html/src/y.py b/tests/farm/html/src/y.py deleted file mode 100644 index a50bb629..00000000 --- a/tests/farm/html/src/y.py +++ /dev/null @@ -1,12 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# A test file for XML reporting by coverage.py. - -def choice(x): - if x < 2: - return 3 - else: - return 4 - -assert choice(1) == 3 diff --git a/tests/farm/run/run_chdir.py b/tests/farm/run/run_chdir.py index 1da4e9a3..5ec0b4ea 100644 --- a/tests/farm/run/run_chdir.py +++ b/tests/farm/run/run_chdir.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt copy("src", "out_chdir") run(""" diff --git a/tests/farm/run/run_timid.py b/tests/farm/run/run_timid.py index 0370cf84..56456618 100644 --- a/tests/farm/run/run_timid.py +++ b/tests/farm/run/run_timid.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Test that the --timid command line argument properly swaps the tracer # function for a simpler one. diff --git a/tests/farm/run/run_xxx.py b/tests/farm/run/run_xxx.py index 1db5b0d0..3caba0ec 100644 --- a/tests/farm/run/run_xxx.py +++ b/tests/farm/run/run_xxx.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt copy("src", "out_xxx") run(""" diff --git a/tests/farm/run/src/chdir.py b/tests/farm/run/src/chdir.py index 35cfcc81..250f7132 100644 --- a/tests/farm/run/src/chdir.py +++ b/tests/farm/run/src/chdir.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt import os print("Line One") diff --git a/tests/farm/run/src/showtrace.py b/tests/farm/run/src/showtrace.py index 3a2750a6..a3692521 100644 --- a/tests/farm/run/src/showtrace.py +++ b/tests/farm/run/src/showtrace.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Show the current frame's trace function, so that we can test what the # command-line options do to the trace function used. diff --git a/tests/farm/run/src/xxx b/tests/farm/run/src/xxx index 864da457..818c7f93 100644 --- a/tests/farm/run/src/xxx +++ b/tests/farm/run/src/xxx @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # This is a python file though it doesn't look like it, like a main script. a = b = c = d = 0 diff --git a/tests/goldtest.py b/tests/goldtest.py index baaa8f01..48842f0c 100644 --- a/tests/goldtest.py +++ b/tests/goldtest.py @@ -1,42 +1,18 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """A test base class for tests based on gold file comparison.""" import os -import sys from unittest_mixins import change_dir # pylint: disable=unused-import -from tests.coveragetest import CoverageTest -from tests.test_farm import clean +from tests.coveragetest import TESTS_DIR # Import helpers, eventually test_farm.py will go away. from tests.test_farm import ( # pylint: disable=unused-import compare, contains, doesnt_contain, contains_any, ) - -class CoverageGoldTest(CoverageTest): - """A test based on gold files.""" - - run_in_temp_dir = False - - def setUp(self): - super(CoverageGoldTest, self).setUp() - self.chdir(self.root_dir) - # Modules should be importable from the current directory. - sys.path.insert(0, '') - - def output_dir(self, the_dir): - """Declare where the output directory is. - - The output directory is deleted at the end of the test, unless the - COVERAGE_KEEP_OUTPUT environment variable is set. - - """ - # To make sure tests are isolated, we always clean the directory at the - # beginning of the test. - clean(the_dir) - - if not os.environ.get("COVERAGE_KEEP_OUTPUT"): # pragma: part covered - self.addCleanup(clean, the_dir) +def gold_path(path): + """Get a path to a gold file for comparison.""" + return os.path.join(TESTS_DIR, "farm", path) diff --git a/tests/helpers.py b/tests/helpers.py index f10169a9..bc9c3982 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Helpers for coverage.py tests.""" diff --git a/tests/js/tests.js b/tests/js/tests.js index bf2ca734..7bd3b9ca 100644 --- a/tests/js/tests.js +++ b/tests/js/tests.js @@ -1,5 +1,5 @@ /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ -/* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ // Tests of coverage.py HTML report chunk navigation. /*global coverage, jQuery, $ */ diff --git a/tests/modules/covmod1.py b/tests/modules/covmod1.py index 0f9638b8..618f1d04 100644 --- a/tests/modules/covmod1.py +++ b/tests/modules/covmod1.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # covmod1.py: Simplest module for testing. i = 1 diff --git a/tests/modules/namespace_420/sub1/__init__.py b/tests/modules/namespace_420/sub1/__init__.py index 94bb2959..4a8721ab 100644 --- a/tests/modules/namespace_420/sub1/__init__.py +++ b/tests/modules/namespace_420/sub1/__init__.py @@ -1,4 +1,4 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt sub1 = "namespace_420 sub1" diff --git a/tests/modules/pkg1/p1a.py b/tests/modules/pkg1/p1a.py index 5d81b1fa..984bf748 100644 --- a/tests/modules/pkg1/p1a.py +++ b/tests/modules/pkg1/p1a.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt import os, sys diff --git a/tests/modules/pkg1/p1b.py b/tests/modules/pkg1/p1b.py index 53505cef..dc6d0b17 100644 --- a/tests/modules/pkg1/p1b.py +++ b/tests/modules/pkg1/p1b.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt x = 1 y = 2 diff --git a/tests/modules/pkg1/p1c.py b/tests/modules/pkg1/p1c.py index 98f319e8..24e2932c 100644 --- a/tests/modules/pkg1/p1c.py +++ b/tests/modules/pkg1/p1c.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt a = 1 b = 2 diff --git a/tests/modules/pkg1/runmod2.py b/tests/modules/pkg1/runmod2.py index 5911db7b..b0f43c2e 100644 --- a/tests/modules/pkg1/runmod2.py +++ b/tests/modules/pkg1/runmod2.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Used in the tests for run_python_module import sys diff --git a/tests/modules/pkg1/sub/ps1a.py b/tests/modules/pkg1/sub/ps1a.py index 44d3b274..b895fc60 100644 --- a/tests/modules/pkg1/sub/ps1a.py +++ b/tests/modules/pkg1/sub/ps1a.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt d = 1 e = 2 diff --git a/tests/modules/pkg1/sub/runmod3.py b/tests/modules/pkg1/sub/runmod3.py index 1f5ce27e..b3b40327 100644 --- a/tests/modules/pkg1/sub/runmod3.py +++ b/tests/modules/pkg1/sub/runmod3.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Used in the tests for run_python_module import sys diff --git a/tests/modules/pkg2/p2a.py b/tests/modules/pkg2/p2a.py index 62caae22..f3f86f99 100644 --- a/tests/modules/pkg2/p2a.py +++ b/tests/modules/pkg2/p2a.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt q = 1 r = 1 diff --git a/tests/modules/pkg2/p2b.py b/tests/modules/pkg2/p2b.py index 73716eb4..38d7a840 100644 --- a/tests/modules/pkg2/p2b.py +++ b/tests/modules/pkg2/p2b.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt t = 1 u = 1 diff --git a/tests/modules/plugins/another.py b/tests/modules/plugins/another.py index 80902d34..dfe03c97 100644 --- a/tests/modules/plugins/another.py +++ b/tests/modules/plugins/another.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """A plugin for tests to reference.""" diff --git a/tests/modules/process_test/try_execfile.py b/tests/modules/process_test/try_execfile.py index ec7dcbe5..706fe39f 100644 --- a/tests/modules/process_test/try_execfile.py +++ b/tests/modules/process_test/try_execfile.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Test file for run_python_file. @@ -68,10 +68,15 @@ FN_VAL = my_function("fooey") loader = globals().get('__loader__') fullname = getattr(loader, 'fullname', None) or getattr(loader, 'name', None) -# A more compact grouped-by-first-letter list of builtins. +# A more compact ad-hoc grouped-by-first-letter list of builtins. +CLUMPS = "ABC,DEF,GHI,JKLMN,OPQR,ST,U,VWXYZ_,ab,cd,efg,hij,lmno,pqr,stuvwxyz".split(",") + def word_group(w): - """Clump AB, CD, EF, etc.""" - return chr((ord(w[0]) + 1) & 0xFE) + """Figure out which CLUMP the first letter of w is in.""" + for i, clump in enumerate(CLUMPS): + if w[0] in clump: + return i + return 99 builtin_dir = [" ".join(s) for _, s in itertools.groupby(dir(__builtins__), key=word_group)] diff --git a/tests/modules/runmod1.py b/tests/modules/runmod1.py index b43b299a..cb1f7e99 100644 --- a/tests/modules/runmod1.py +++ b/tests/modules/runmod1.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # Used in the tests for run_python_module import sys diff --git a/tests/modules/usepkgs.py b/tests/modules/usepkgs.py index 222e68ce..63ce7c18 100644 --- a/tests/modules/usepkgs.py +++ b/tests/modules/usepkgs.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt import pkg1.p1a, pkg1.p1b, pkg1.sub import pkg2.p2a, pkg2.p2b diff --git a/tests/moremodules/namespace_420/sub2/__init__.py b/tests/moremodules/namespace_420/sub2/__init__.py index 0839688c..b5e413cc 100644 --- a/tests/moremodules/namespace_420/sub2/__init__.py +++ b/tests/moremodules/namespace_420/sub2/__init__.py @@ -1,4 +1,4 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt sub2 = "namespace_420 sub2" diff --git a/tests/moremodules/othermods/othera.py b/tests/moremodules/othermods/othera.py index b3ee9c04..56cdfe80 100644 --- a/tests/moremodules/othermods/othera.py +++ b/tests/moremodules/othermods/othera.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt o = 1 p = 2 diff --git a/tests/moremodules/othermods/otherb.py b/tests/moremodules/othermods/otherb.py index 334fdc4a..50ba96a3 100644 --- a/tests/moremodules/othermods/otherb.py +++ b/tests/moremodules/othermods/otherb.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt q = 3 r = 4 diff --git a/tests/moremodules/othermods/sub/osa.py b/tests/moremodules/othermods/sub/osa.py index 4005640e..5e5a5ff5 100644 --- a/tests/moremodules/othermods/sub/osa.py +++ b/tests/moremodules/othermods/sub/osa.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt s = 5 t = 6 diff --git a/tests/moremodules/othermods/sub/osb.py b/tests/moremodules/othermods/sub/osb.py index 7d96fb79..c150087d 100644 --- a/tests/moremodules/othermods/sub/osb.py +++ b/tests/moremodules/othermods/sub/osb.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt u = 7 v = 8 diff --git a/tests/osinfo.py b/tests/osinfo.py index 094fb097..f9562deb 100644 --- a/tests/osinfo.py +++ b/tests/osinfo.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """OS information for testing.""" diff --git a/tests/plugin1.py b/tests/plugin1.py index e03dadf1..a070af36 100644 --- a/tests/plugin1.py +++ b/tests/plugin1.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """A file tracer plugin for test_plugins.py to import.""" diff --git a/tests/plugin2.py b/tests/plugin2.py index c1ab1c23..c334628a 100644 --- a/tests/plugin2.py +++ b/tests/plugin2.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """A file tracer plugin for test_plugins.py to import.""" diff --git a/tests/plugin_config.py b/tests/plugin_config.py index 67a790a2..c4d3cf65 100644 --- a/tests/plugin_config.py +++ b/tests/plugin_config.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """A configuring plugin for test_plugins.py to import.""" diff --git a/tests/stress_phystoken.tok b/tests/stress_phystoken.tok index eb2fb669..f2b190c3 100644 --- a/tests/stress_phystoken.tok +++ b/tests/stress_phystoken.tok @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt # 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 diff --git a/tests/stress_phystoken_dos.tok b/tests/stress_phystoken_dos.tok index 5b016a77..eb937d95 100644 --- a/tests/stress_phystoken_dos.tok +++ b/tests/stress_phystoken_dos.tok @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
-# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
# 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
diff --git a/tests/test_api.py b/tests/test_api.py index b461c503..b44a5d34 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,10 +1,11 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.py's API.""" import fnmatch import os +import os.path import sys import textwrap import warnings @@ -12,6 +13,7 @@ import warnings import coverage from coverage import env from coverage.backward import StringIO, import_local_file +from coverage.data import line_counts from coverage.misc import CoverageException from coverage.report import Reporter @@ -356,6 +358,7 @@ class ApiTest(CoverageTest): cov = coverage.Coverage(data_suffix=True) self.start_import_stop(cov, "code2") cov.save() + self.assert_file_count(".coverage.*", 2) def make_bad_data_file(self): """Make one bad data file.""" @@ -368,8 +371,12 @@ class ApiTest(CoverageTest): self.make_bad_data_file() cov = coverage.Coverage() warning_regex = ( + r"(" # JSON message: r"Couldn't read data from '.*\.coverage\.foo': " r"CoverageException: Doesn't seem to be a coverage\.py data file" + r"|" # SQL message: + r"Couldn't use data file '.*\.coverage\.foo': file (is encrypted or )?is not a database" + r")" ) with self.assert_warnings(cov, [warning_regex]): cov.combine() @@ -377,8 +384,9 @@ class ApiTest(CoverageTest): # We got the results from code1 and code2 properly. self.check_code1_code2(cov) - # The bad file still exists. + # The bad file still exists, but it's the only parallel data file left. self.assert_exists(".coverage.foo") + self.assert_file_count(".coverage.*", 1) def test_combining_twice(self): self.make_good_data_files() @@ -386,6 +394,8 @@ class ApiTest(CoverageTest): cov1.combine() cov1.save() self.check_code1_code2(cov1) + self.assert_file_count(".coverage.*", 0) + self.assert_exists(".coverage") cov2 = coverage.Coverage() with self.assertRaisesRegex(CoverageException, r"No data to combine"): @@ -502,8 +512,6 @@ class NamespaceModuleTest(UsingModulesMixin, CoverageTest): class OmitIncludeTestsMixin(UsingModulesMixin, CoverageTestMethodsMixin): """Test methods for coverage methods taking include and omit.""" - run_in_temp_dir = False - def filenames_in(self, summary, filenames): """Assert the `filenames` are in the keys of `summary`.""" for filename in filenames.split(): @@ -571,7 +579,7 @@ class SourceOmitIncludeTest(OmitIncludeTestsMixin, CoverageTest): import usepkgs # pragma: nested # pylint: disable=import-error, unused-variable cov.stop() # pragma: nested data = cov.get_data() - summary = data.line_counts() + summary = line_counts(data) for k, v in list(summary.items()): assert k.endswith(".py") summary[k[:-3]] = v @@ -581,7 +589,7 @@ class SourceOmitIncludeTest(OmitIncludeTestsMixin, CoverageTest): cov = coverage.Coverage(source=["pkg1"], include=["pkg2"]) with self.assert_warnings(cov, ["--include is ignored because --source is set"]): cov.start() - cov.stop() + cov.stop() # pragma: nested def test_source_package_as_dir(self): # pkg1 is a directory, since we cd'd into tests/modules in setUp. @@ -696,8 +704,6 @@ class TestRunnerPluginTest(CoverageTest): """ 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 @@ -705,6 +711,7 @@ class TestRunnerPluginTest(CoverageTest): c = 4 """) + cov = coverage.Coverage() if erase: cov.combine() cov.erase() @@ -725,6 +732,43 @@ class TestRunnerPluginTest(CoverageTest): def test_nose_plugin_with_erase(self): self.pretend_to_be_nose_with_cover(erase=True) + def test_pytestcov_parallel(self): + self.pretend_to_be_pytestcov(append=False) + + def test_pytestcov_parallel_append(self): + self.pretend_to_be_pytestcov(append=True) + + def pretend_to_be_pytestcov(self, append): + self.make_file("prog.py", """\ + a = 1 + b = 2 + if b == 1: + c = 4 + """) + self.make_file(".coveragerc", """\ + [run] + parallel = True + source = . + """) + + cov = coverage.Coverage(source=None, branch=None, config_file='.coveragerc') + if append: + cov.load() + else: + cov.erase() + self.start_import_stop(cov, "prog") + cov.combine() + cov.save() + report = StringIO() + cov.report(show_missing=None, ignore_errors=True, file=report, skip_covered=None) + self.assertEqual(report.getvalue(), textwrap.dedent("""\ + Name Stmts Miss Cover + ----------------------------- + prog.py 4 1 75% + """)) + self.assert_file_count(".coverage", 0) + self.assert_file_count(".coverage.*", 1) + class ReporterDeprecatedAttributeTest(CoverageTest): """Test that Reporter.file_reporters has been deprecated.""" diff --git a/tests/test_arcs.py b/tests/test_arcs.py index ef71ea16..c86147b7 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.py's arc measurement.""" @@ -407,8 +407,6 @@ class LoopArcTest(CoverageTest): ) def test_other_comprehensions(self): - if env.PYVERSION < (2, 7): - self.skipTest("No set or dict comprehensions before 2.7") # Set comprehension: self.check_coverage("""\ o = ((1,2), (3,4)) @@ -431,8 +429,6 @@ class LoopArcTest(CoverageTest): ) def test_multiline_dict_comp(self): - if env.PYVERSION < (2, 7): - self.skipTest("No set or dict comprehensions before 2.7") if env.PYVERSION < (3, 5): arcz = "-42 2B B-4 2-4" else: diff --git a/tests/test_backward.py b/tests/test_backward.py index bbecb780..8acb8707 100644 --- a/tests/test_backward.py +++ b/tests/test_backward.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests that our version shims in backward.py are working.""" diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 1b7c6653..39827ff5 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Test cmdline.py for coverage.py.""" @@ -16,7 +16,7 @@ import coverage import coverage.cmdline from coverage import env from coverage.config import CoverageConfig -from coverage.data import CoverageData, CoverageDataFiles +from coverage.data import CoverageData from coverage.misc import ExceptionDuringRun from tests.coveragetest import CoverageTest, OK, ERR, command_line @@ -33,7 +33,7 @@ class BaseCmdLineTest(CoverageTest): defaults.Coverage( cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None, - concurrency=None, + concurrency=None, check_preimported=True, context=None, ) defaults.annotate( directory=None, ignore_errors=None, include=None, omit=None, morfs=[], @@ -70,34 +70,31 @@ class BaseCmdLineTest(CoverageTest): return mk - def mock_command_line(self, args, path_exists=None): + 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() - m.path_exists.return_value = path_exists ret = command_line( args, _covpkg=m, _run_python_file=m.run_python_file, _run_python_module=m.run_python_module, _help_fn=m.help_fn, - _path_exists=m.path_exists, ) return m, ret - def cmd_executes(self, args, code, ret=OK, path_exists=None): + 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, path_exists=path_exists) + m1, r1 = self.mock_command_line(args) self.assertEqual(r1, ret, "Wrong status: got %r, wanted %r" % (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() - m2.path_exists.return_value = path_exists code_obj = compile(code, "<code>", "exec") eval(code_obj, globals(), {'m2': m2}) # pylint: disable=eval-used @@ -235,7 +232,7 @@ class CmdLineTest(BaseCmdLineTest): self.command_line("debug sys") out = self.stdout() self.assertIn("version:", out) - self.assertIn("data_path:", out) + self.assertIn("data_file:", out) def test_debug_config(self): self.command_line("debug config") @@ -358,7 +355,6 @@ class CmdLineTest(BaseCmdLineTest): # run calls coverage.erase first. self.cmd_executes("run foo.py", """\ .Coverage() - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -367,26 +363,24 @@ class CmdLineTest(BaseCmdLineTest): # run -a combines with an existing data file before saving. self.cmd_executes("run -a foo.py", """\ .Coverage() + .load() .start() .run_python_file('foo.py', ['foo.py']) .stop() - .path_exists('.coverage') - .combine(data_paths=['.coverage']) .save() - """, path_exists=True) + """) # run -a doesn't combine anything if the data file doesn't exist. self.cmd_executes("run -a foo.py", """\ .Coverage() + .load() .start() .run_python_file('foo.py', ['foo.py']) .stop() - .path_exists('.coverage') .save() - """, path_exists=False) + """) # --timid sets a flag, and program arguments get passed through. self.cmd_executes("run --timid foo.py abc 123", """\ .Coverage(timid=True) - .erase() .start() .run_python_file('foo.py', ['foo.py', 'abc', '123']) .stop() @@ -395,7 +389,6 @@ class CmdLineTest(BaseCmdLineTest): # -L sets a flag, and flags for the program don't confuse us. self.cmd_executes("run -p -L foo.py -a -b", """\ .Coverage(cover_pylib=True, data_suffix=True) - .erase() .start() .run_python_file('foo.py', ['foo.py', '-a', '-b']) .stop() @@ -403,7 +396,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --branch foo.py", """\ .Coverage(branch=True) - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -411,7 +403,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --rcfile=myrc.rc foo.py", """\ .Coverage(config_file="myrc.rc") - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -419,7 +410,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --include=pre1,pre2 foo.py", """\ .Coverage(include=["pre1", "pre2"]) - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -427,7 +417,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --omit=opre1,opre2 foo.py", """\ .Coverage(omit=["opre1", "opre2"]) - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -435,7 +424,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --include=pre1,pre2 --omit=opre1,opre2 foo.py", """\ .Coverage(include=["pre1", "pre2"], omit=["opre1", "opre2"]) - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -443,7 +431,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --source=quux,hi.there,/home/bar foo.py", """\ .Coverage(source=["quux", "hi.there", "/home/bar"]) - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -451,7 +438,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --concurrency=gevent foo.py", """\ .Coverage(concurrency='gevent') - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -459,7 +445,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --concurrency=multiprocessing foo.py", """\ .Coverage(concurrency='multiprocessing') - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -485,14 +470,17 @@ class CmdLineTest(BaseCmdLineTest): # config file. self.command_line("run --concurrency=multiprocessing --branch foo.py", ret=ERR) self.assertIn( - "Options affecting multiprocessing must be specified in a configuration file.", + "Options affecting multiprocessing must only be specified in a configuration file.", + self.stderr() + ) + self.assertIn( + "Remove --branch from the command line.", self.stderr() ) def test_run_debug(self): self.cmd_executes("run --debug=opt1 foo.py", """\ .Coverage(debug=["opt1"]) - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -500,7 +488,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --debug=opt1,opt2 foo.py", """\ .Coverage(debug=["opt1","opt2"]) - .erase() .start() .run_python_file('foo.py', ['foo.py']) .stop() @@ -510,7 +497,6 @@ class CmdLineTest(BaseCmdLineTest): def test_run_module(self): self.cmd_executes("run -m mymodule", """\ .Coverage() - .erase() .start() .run_python_module('mymodule', ['mymodule']) .stop() @@ -518,7 +504,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run -m mymodule -qq arg1 arg2", """\ .Coverage() - .erase() .start() .run_python_module('mymodule', ['mymodule', '-qq', 'arg1', 'arg2']) .stop() @@ -526,7 +511,6 @@ class CmdLineTest(BaseCmdLineTest): """) self.cmd_executes("run --branch -m mymodule", """\ .Coverage(branch=True) - .erase() .start() .run_python_module('mymodule', ['mymodule']) .stop() @@ -605,8 +589,7 @@ class CmdLineWithFilesTest(BaseCmdLineTest): "file2.py": dict.fromkeys(range(1, 24)), }) data.add_file_tracers({"file1.py": "a_plugin"}) - data_files = CoverageDataFiles() - data_files.write(data) + data.write() self.command_line("debug data") self.assertMultiLineEqual(self.stdout(), textwrap.dedent("""\ @@ -617,16 +600,16 @@ class CmdLineWithFilesTest(BaseCmdLineTest): 2 files: file1.py: 17 lines [a_plugin] file2.py: 23 lines - """).replace("FILENAME", data_files.filename)) + """).replace("FILENAME", data.filename)) def test_debug_data_with_no_data(self): - data_files = CoverageDataFiles() + data = CoverageData() self.command_line("debug data") self.assertMultiLineEqual(self.stdout(), textwrap.dedent("""\ -- data ------------------------------------------------------ path: FILENAME No data collected - """).replace("FILENAME", data_files.filename)) + """).replace("FILENAME", data.filename)) class CmdLineStdoutTest(BaseCmdLineTest): diff --git a/tests/test_collector.py b/tests/test_collector.py index bd963415..9989b229 100644 --- a/tests/test_collector.py +++ b/tests/test_collector.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests of coverage/collector.py and other collectors.""" diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 71006042..9e2d73d9 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for concurrency libraries.""" @@ -14,6 +14,7 @@ from flaky import flaky import coverage from coverage import env from coverage.backward import import_local_file +from coverage.data import line_counts from coverage.files import abs_file from tests.coveragetest import CoverageTest @@ -235,8 +236,8 @@ class ConcurrencyTest(CoverageTest): # Read the coverage file and see that try_it.py has all its lines # executed. - data = coverage.CoverageData() - data.read_file(".coverage") + data = coverage.CoverageData(".coverage") + data.read() # If the test fails, it's helpful to see this info: fname = abs_file("try_it.py") @@ -245,7 +246,7 @@ class ConcurrencyTest(CoverageTest): print_simple_annotation(code, linenos) lines = line_count(code) - self.assertEqual(data.line_counts()['try_it.py'], lines) + self.assertEqual(line_counts(data)['try_it.py'], lines) def test_threads(self): code = (THREAD + SUM_RANGE_Q + PRINT_SUM_RANGE).format(QLIMIT=self.QLIMIT) @@ -335,7 +336,7 @@ MULTI_CODE = """ import sys def process_worker_main(args): - # Need to pause, or the tasks go too quick, and some processes + # Need to pause, or the tasks go too quickly, and some processes # in the pool don't get any work, and then don't record data. time.sleep(0.02) ret = work(*args) @@ -359,7 +360,7 @@ MULTI_CODE = """ """ -@flaky(max_runs=10) # Sometimes a test fails due to inherent randomness. Try one more time. +@flaky(max_runs=10) # Sometimes a test fails due to inherent randomness. Try more times. class MultiprocessingTest(CoverageTest): """Test support of the multiprocessing module.""" @@ -403,7 +404,7 @@ class MultiprocessingTest(CoverageTest): last_line = self.squeezed_lines(out)[-1] self.assertRegex(last_line, r"multi.py \d+ 0 100%") - def test_multiprocessing(self): + def test_multiprocessing_simple(self): nprocs = 3 upto = 30 code = (SQUARE_OR_CUBE_WORK + MULTI_CODE).format(NPROCS=nprocs, UPTO=upto) @@ -430,6 +431,7 @@ class MultiprocessingTest(CoverageTest): [run] concurrency = multiprocessing branch = True + omit = */site-packages/* """) if env.PYVERSION >= (3, 4): @@ -464,7 +466,7 @@ def test_coverage_stop_in_threads(): has_started_coverage = [] has_stopped_coverage = [] - def run_thread(): + def run_thread(): # pragma: nested """Check that coverage is stopping properly in threads.""" deadline = time.time() + 5 ident = threading.currentThread().ident @@ -480,11 +482,11 @@ def test_coverage_stop_in_threads(): cov = coverage.coverage() cov.start() - t = threading.Thread(target=run_thread) - t.start() + t = threading.Thread(target=run_thread) # pragma: nested + t.start() # pragma: nested - time.sleep(0.1) - cov.stop() + time.sleep(0.1) # pragma: nested + cov.stop() # pragma: nested time.sleep(0.1) assert has_started_coverage == [t.ident] @@ -513,7 +515,7 @@ def test_thread_safe_save_data(tmpdir): for module_name in module_names: import_local_file(module_name) - def random_load(): + def random_load(): # pragma: nested """Import modules randomly to stress coverage.""" while should_run[0]: module_name = random.choice(module_names) @@ -529,12 +531,12 @@ def test_thread_safe_save_data(tmpdir): cov = coverage.coverage() cov.start() - threads = [threading.Thread(target=random_load) for _ in range(10)] - should_run[0] = True - for t in threads: + threads = [threading.Thread(target=random_load) for _ in range(10)] # pragma: nested + should_run[0] = True # pragma: nested + for t in threads: # pragma: nested t.start() - time.sleep(duration) + time.sleep(duration) # pragma: nested cov.stop() @@ -546,7 +548,7 @@ def test_thread_safe_save_data(tmpdir): for t in threads: t.join() - if (not imported) and duration < 10: + if (not imported) and duration < 10: # pragma: only failure duration *= 2 finally: diff --git a/tests/test_config.py b/tests/test_config.py index 0b4d40b6..513522ee 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Test the config file handling for coverage.py""" @@ -103,6 +103,21 @@ class ConfigTest(CoverageTest): cov = coverage.Coverage() self.assertEqual(cov.config.debug, ["dataio", "pids", "callers", "fooey"]) + def test_rcfile_from_environment(self): + self.make_file("here.ini", """\ + [run] + data_file = overthere.dat + """) + self.set_environ("COVERAGE_RCFILE", "here.ini") + cov = coverage.Coverage() + self.assertEqual(cov.config.data_file, "overthere.dat") + + def test_missing_rcfile_from_environment(self): + self.set_environ("COVERAGE_RCFILE", "nowhere.ini") + msg = "Couldn't read 'nowhere.ini' as a config file" + with self.assertRaisesRegex(CoverageException, msg): + coverage.Coverage() + def test_parse_errors(self): # Im-parsable values raise CoverageException, with details. bad_configs_and_msgs = [ diff --git a/tests/test_context.py b/tests/test_context.py new file mode 100644 index 00000000..4339d336 --- /dev/null +++ b/tests/test_context.py @@ -0,0 +1,184 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Tests for context support.""" + +import os.path + +import coverage +from coverage import env +from coverage.data import CoverageData +from coverage.misc import CoverageException + +from tests.coveragetest import CoverageTest + + +class StaticContextTest(CoverageTest): + """Tests of the static context.""" + + def setUp(self): + super(StaticContextTest, self).setUp() + self.skip_unless_data_storage_is("sql") + + def test_no_context(self): + self.make_file("main.py", "a = 1") + cov = coverage.Coverage() + self.start_import_stop(cov, "main") + data = cov.get_data() + self.assertCountEqual(data.measured_contexts(), [""]) + + def test_static_context(self): + self.make_file("main.py", "a = 1") + cov = coverage.Coverage(context="gooey") + self.start_import_stop(cov, "main") + data = cov.get_data() + self.assertCountEqual(data.measured_contexts(), ["gooey"]) + + SOURCE = """\ + a = 1 + if a > 2: + a = 3 + assert a == 1 + """ + + LINES = [1, 2, 4] + ARCS = [(-1, 1), (1, 2), (2, 4), (4, -1)] + + def run_red_blue(self, **options): + """Run red.py and blue.py, and return their CoverageData objects.""" + self.make_file("red.py", self.SOURCE) + red_cov = coverage.Coverage(context="red", data_suffix="r", source=["."], **options) + self.start_import_stop(red_cov, "red") + red_cov.save() + red_data = red_cov.get_data() + + self.make_file("blue.py", self.SOURCE) + blue_cov = coverage.Coverage(context="blue", data_suffix="b", source=["."], **options) + self.start_import_stop(blue_cov, "blue") + blue_cov.save() + blue_data = blue_cov.get_data() + + return red_data, blue_data + + def test_combining_line_contexts(self): + red_data, blue_data = self.run_red_blue() + for datas in [[red_data, blue_data], [blue_data, red_data]]: + combined = CoverageData(suffix="combined") + for data in datas: + combined.update(data) + + self.assertEqual(combined.measured_contexts(), {'red', 'blue'}) + + full_names = {os.path.basename(f): f for f in combined.measured_files()} + self.assertCountEqual(full_names, ['red.py', 'blue.py']) + + fred = full_names['red.py'] + fblue = full_names['blue.py'] + + self.assertEqual(combined.lines(fred, context='red'), self.LINES) + self.assertEqual(combined.lines(fred, context='blue'), []) + self.assertEqual(combined.lines(fblue, context='red'), []) + self.assertEqual(combined.lines(fblue, context='blue'), self.LINES) + + def test_combining_arc_contexts(self): + red_data, blue_data = self.run_red_blue(branch=True) + for datas in [[red_data, blue_data], [blue_data, red_data]]: + combined = CoverageData(suffix="combined") + for data in datas: + combined.update(data) + + self.assertEqual(combined.measured_contexts(), {'red', 'blue'}) + + full_names = {os.path.basename(f): f for f in combined.measured_files()} + self.assertCountEqual(full_names, ['red.py', 'blue.py']) + + fred = full_names['red.py'] + fblue = full_names['blue.py'] + + self.assertEqual(combined.lines(fred, context='red'), self.LINES) + self.assertEqual(combined.lines(fred, context='blue'), []) + self.assertEqual(combined.lines(fblue, context='red'), []) + self.assertEqual(combined.lines(fblue, context='blue'), self.LINES) + + self.assertEqual(combined.arcs(fred, context='red'), self.ARCS) + self.assertEqual(combined.arcs(fred, context='blue'), []) + self.assertEqual(combined.arcs(fblue, context='red'), []) + self.assertEqual(combined.arcs(fblue, context='blue'), self.ARCS) + + +class DynamicContextTest(CoverageTest): + """Tests of dynamically changing contexts.""" + + def setUp(self): + super(DynamicContextTest, self).setUp() + self.skip_unless_data_storage_is("sql") + if not env.C_TRACER: + self.skipTest("Only the C tracer supports dynamic contexts") + + SOURCE = """\ + def helper(lineno): + x = 2 + + def test_one(): + a = 5 + helper(6) + + def test_two(): + a = 9 + b = 10 + if a > 11: + b = 12 + assert a == (13-4) + assert b == (14-4) + helper(15) + + test_one() + x = 18 + helper(19) + test_two() + """ + + OUTER_LINES = [1, 4, 8, 17, 18, 19, 2, 20] + TEST_ONE_LINES = [5, 6, 2] + TEST_TWO_LINES = [9, 10, 11, 13, 14, 15, 2] + + def test_dynamic_alone(self): + self.make_file("two_tests.py", self.SOURCE) + cov = coverage.Coverage(source=["."]) + cov.set_option("run:dynamic_context", "test_function") + self.start_import_stop(cov, "two_tests") + data = cov.get_data() + + fname = os.path.abspath("two_tests.py") + self.assertCountEqual(data.measured_contexts(), ["", "test_one", "test_two"]) + self.assertCountEqual(data.lines(fname, ""), self.OUTER_LINES) + self.assertCountEqual(data.lines(fname, "test_one"), self.TEST_ONE_LINES) + self.assertCountEqual(data.lines(fname, "test_two"), self.TEST_TWO_LINES) + + def test_static_and_dynamic(self): + self.make_file("two_tests.py", self.SOURCE) + cov = coverage.Coverage(context="stat", source=["."]) + cov.set_option("run:dynamic_context", "test_function") + self.start_import_stop(cov, "two_tests") + data = cov.get_data() + + fname = os.path.abspath("two_tests.py") + self.assertCountEqual(data.measured_contexts(), ["stat", "stat:test_one", "stat:test_two"]) + self.assertCountEqual(data.lines(fname, "stat"), self.OUTER_LINES) + self.assertCountEqual(data.lines(fname, "stat:test_one"), self.TEST_ONE_LINES) + self.assertCountEqual(data.lines(fname, "stat:test_two"), self.TEST_TWO_LINES) + + +class DynamicContextWithPythonTracerTest(CoverageTest): + """The Python tracer doesn't do dynamic contexts at all.""" + + run_in_temp_dir = False + + def test_python_tracer_fails_properly(self): + if env.C_TRACER: + self.skipTest("This test is specifically about the Python tracer.") + cov = coverage.Coverage() + cov.set_option("run:dynamic_context", "test_function") + msg = r"Can't support dynamic contexts with PyTracer" + with self.assertRaisesRegex(CoverageException, msg): + cov.start() diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 45abb2be..676fc831 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.py.""" @@ -582,12 +582,7 @@ class SimpleStatementTest(CoverageTest): """, [2, 3] ) - if env.PYVERSION < (3, 7): - # Before 3.7, module docstrings were included in the lnotab table, - # unless they were the first line in the file? - lines = [2, 3, 4] - else: - lines = [3, 4] + lines = [2, 3, 4] self.check_coverage("""\ # Start with a comment, because it changes the behavior(!?) '''I am a module docstring.''' @@ -1147,11 +1142,7 @@ class CompoundStatementTest(CoverageTest): [1,10,12,13], "") def test_class_def(self): - if env.PYVERSION < (3, 7): - arcz="-22 2D DE E-2 23 36 6A A-2 -68 8-6 -AB B-A" - else: - # Python 3.7 no longer includes class docstrings in the lnotab table. - arcz="-22 2D DE E-2 26 6A A-2 -68 8-6 -AB B-A" + arcz="-22 2D DE E-2 23 36 6A A-2 -68 8-6 -AB B-A" self.check_coverage("""\ # A comment. class theClass: diff --git a/tests/test_data.py b/tests/test_data.py index 46999f63..60fd341e 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.data""" @@ -8,11 +8,12 @@ import json import os import os.path import re +import sqlite3 import mock -from coverage.backward import StringIO -from coverage.data import CoverageData, CoverageDataFiles, debug_main, canonicalize_json_data +from coverage.data import CoverageData, debug_main, canonicalize_json_data, combine_parallel_data +from coverage.data import add_data_to_hash, line_counts, STORAGE from coverage.debug import DebugControlString from coverage.files import PathAliases, canonical_filename from coverage.misc import CoverageException @@ -74,9 +75,9 @@ MEASURED_FILES_3_4 = ['x.py', 'y.py', 'z.py'] class DataTestHelpers(CoverageTest): """Test helpers for data tests.""" - def assert_line_counts(self, covdata, line_counts, fullpath=False): - """Check that the line_counts of `covdata` is `line_counts`.""" - self.assertEqual(covdata.line_counts(fullpath), line_counts) + def assert_line_counts(self, covdata, counts, fullpath=False): + """Check that the line_counts of `covdata` is `counts`.""" + self.assertEqual(line_counts(covdata, fullpath), counts) def assert_measured_files(self, covdata, measured): """Check that `covdata`'s measured files are `measured`.""" @@ -105,7 +106,10 @@ class DataTestHelpers(CoverageTest): class CoverageDataTest(DataTestHelpers, CoverageTest): """Test cases for CoverageData.""" - run_in_temp_dir = False + # SQL data storage always has files on disk, even without .write(). + # We need to separate the tests so they don't clobber each other. + run_in_temp_dir = STORAGE == "sql" + no_files_in_temp_dir = True def test_empty_data_is_false(self): covdata = CoverageData() @@ -187,6 +191,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): self.assertIsNone(covdata.lines('no_such_file.py')) def test_run_info(self): + self.skip_unless_data_storage_is("json") covdata = CoverageData() self.assertEqual(covdata.run_infos(), []) covdata.add_run_info(hello="there") @@ -230,18 +235,18 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): covdata.add_lines({"p1.foo": dict.fromkeys([1, 2, 3])}) covdata.add_file_tracers({"p1.foo": "p1.plugin"}) - msg = "Conflicting file tracer name for 'p1.foo': 'p1.plugin' vs 'p1.plugin.foo'" + msg = "Conflicting file tracer name for 'p1.foo': u?'p1.plugin' vs u?'p1.plugin.foo'" with self.assertRaisesRegex(CoverageException, msg): covdata.add_file_tracers({"p1.foo": "p1.plugin.foo"}) def test_update_lines(self): - covdata1 = CoverageData() + covdata1 = CoverageData(suffix='1') covdata1.add_lines(LINES_1) - covdata2 = CoverageData() + covdata2 = CoverageData(suffix='2') covdata2.add_lines(LINES_2) - covdata3 = CoverageData() + covdata3 = CoverageData(suffix='3') covdata3.update(covdata1) covdata3.update(covdata2) @@ -250,13 +255,13 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): self.assertEqual(covdata3.run_infos(), []) def test_update_arcs(self): - covdata1 = CoverageData() + covdata1 = CoverageData(suffix='1') covdata1.add_arcs(ARCS_3) - covdata2 = CoverageData() + covdata2 = CoverageData(suffix='2') covdata2.add_arcs(ARCS_4) - covdata3 = CoverageData() + covdata3 = CoverageData(suffix='3') covdata3.update(covdata1) covdata3.update(covdata2) @@ -265,6 +270,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): self.assertEqual(covdata3.run_infos(), []) def test_update_run_info(self): + self.skip_unless_data_storage_is("json") covdata1 = CoverageData() covdata1.add_arcs(ARCS_3) covdata1.add_run_info(hello="there", count=17) @@ -283,10 +289,10 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): ]) def test_update_cant_mix_lines_and_arcs(self): - covdata1 = CoverageData() + covdata1 = CoverageData(suffix='1') covdata1.add_lines(LINES_1) - covdata2 = CoverageData() + covdata2 = CoverageData(suffix='2') covdata2.add_arcs(ARCS_3) with self.assertRaisesRegex(CoverageException, "Can't combine arc data with line data"): @@ -296,7 +302,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): covdata2.update(covdata1) def test_update_file_tracers(self): - covdata1 = CoverageData() + covdata1 = CoverageData(suffix='1') covdata1.add_lines({ "p1.html": dict.fromkeys([1, 2, 3, 4]), "p2.html": dict.fromkeys([5, 6, 7]), @@ -307,7 +313,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): "p2.html": "html.plugin2", }) - covdata2 = CoverageData() + covdata2 = CoverageData(suffix='2') covdata2.add_lines({ "p1.html": dict.fromkeys([3, 4, 5, 6]), "p2.html": dict.fromkeys([7, 8, 9]), @@ -320,7 +326,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): "p3.foo": "foo_plugin", }) - covdata3 = CoverageData() + covdata3 = CoverageData(suffix='3') covdata3.update(covdata1) covdata3.update(covdata2) self.assertEqual(covdata3.file_tracer("p1.html"), "html.plugin") @@ -329,43 +335,50 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): self.assertEqual(covdata3.file_tracer("main.py"), "") def test_update_conflicting_file_tracers(self): - covdata1 = CoverageData() + covdata1 = CoverageData(suffix='1') covdata1.add_lines({"p1.html": dict.fromkeys([1, 2, 3])}) covdata1.add_file_tracers({"p1.html": "html.plugin"}) - covdata2 = CoverageData() + covdata2 = CoverageData(suffix='2') covdata2.add_lines({"p1.html": dict.fromkeys([1, 2, 3])}) covdata2.add_file_tracers({"p1.html": "html.other_plugin"}) - msg = "Conflicting file tracer name for 'p1.html': 'html.plugin' vs 'html.other_plugin'" + msg = "Conflicting file tracer name for 'p1.html': u?'html.plugin' vs u?'html.other_plugin'" with self.assertRaisesRegex(CoverageException, msg): covdata1.update(covdata2) - msg = "Conflicting file tracer name for 'p1.html': 'html.other_plugin' vs 'html.plugin'" + msg = "Conflicting file tracer name for 'p1.html': u?'html.other_plugin' vs u?'html.plugin'" with self.assertRaisesRegex(CoverageException, msg): covdata2.update(covdata1) def test_update_file_tracer_vs_no_file_tracer(self): - covdata1 = CoverageData() + covdata1 = CoverageData(suffix="1") covdata1.add_lines({"p1.html": dict.fromkeys([1, 2, 3])}) covdata1.add_file_tracers({"p1.html": "html.plugin"}) - covdata2 = CoverageData() + covdata2 = CoverageData(suffix="2") covdata2.add_lines({"p1.html": dict.fromkeys([1, 2, 3])}) - msg = "Conflicting file tracer name for 'p1.html': 'html.plugin' vs ''" + msg = "Conflicting file tracer name for 'p1.html': u?'html.plugin' vs u?''" with self.assertRaisesRegex(CoverageException, msg): covdata1.update(covdata2) - msg = "Conflicting file tracer name for 'p1.html': '' vs 'html.plugin'" + msg = "Conflicting file tracer name for 'p1.html': u?'' vs u?'html.plugin'" with self.assertRaisesRegex(CoverageException, msg): covdata2.update(covdata1) + def test_asking_isnt_measuring(self): + # Asking about an unmeasured file shouldn't make it seem measured. + covdata = CoverageData() + self.assert_measured_files(covdata, []) + self.assertEqual(covdata.arcs("missing.py"), None) + self.assert_measured_files(covdata, []) + def test_add_to_hash_with_lines(self): covdata = CoverageData() covdata.add_lines(LINES_1) hasher = mock.Mock() - covdata.add_to_hash("a.py", hasher) + add_data_to_hash(covdata, "a.py", hasher) self.assertEqual(hasher.method_calls, [ mock.call.update([1, 2]), # lines mock.call.update(""), # file_tracer name @@ -376,7 +389,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): covdata.add_arcs(ARCS_3) covdata.add_file_tracers({"y.py": "hologram_plugin"}) hasher = mock.Mock() - covdata.add_to_hash("y.py", hasher) + add_data_to_hash(covdata, "y.py", hasher) self.assertEqual(hasher.method_calls, [ mock.call.update([(-1, 17), (17, 23), (23, -1)]), # arcs mock.call.update("hologram_plugin"), # file_tracer name @@ -387,7 +400,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): covdata = CoverageData() covdata.add_lines(LINES_1) hasher = mock.Mock() - covdata.add_to_hash("missing.py", hasher) + add_data_to_hash(covdata, "missing.py", hasher) self.assertEqual(hasher.method_calls, [ mock.call.update([]), mock.call.update(None), @@ -399,7 +412,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): covdata.add_arcs(ARCS_3) covdata.add_file_tracers({"y.py": "hologram_plugin"}) hasher = mock.Mock() - covdata.add_to_hash("missing.py", hasher) + add_data_to_hash(covdata, "missing.py", hasher) self.assertEqual(hasher.method_calls, [ mock.call.update([]), mock.call.update(None), @@ -420,12 +433,10 @@ class CoverageDataTest(DataTestHelpers, CoverageTest): def test_read_and_write_are_opposites(self): covdata1 = CoverageData() covdata1.add_arcs(ARCS_3) - stringio = StringIO() - covdata1.write_fileobj(stringio) + covdata1.write() - stringio.seek(0) covdata2 = CoverageData() - covdata2.read_fileobj(stringio) + covdata2.read() self.assert_arcs3_data(covdata2) @@ -433,59 +444,81 @@ class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest): """Tests of CoverageData that need a temporary directory to make files.""" def test_read_write_lines(self): - covdata1 = CoverageData() + covdata1 = CoverageData("lines.dat") covdata1.add_lines(LINES_1) - covdata1.write_file("lines.dat") + covdata1.write() - covdata2 = CoverageData() - covdata2.read_file("lines.dat") + covdata2 = CoverageData("lines.dat") + covdata2.read() self.assert_lines1_data(covdata2) def test_read_write_arcs(self): - covdata1 = CoverageData() + covdata1 = CoverageData("arcs.dat") covdata1.add_arcs(ARCS_3) - covdata1.write_file("arcs.dat") + covdata1.write() - covdata2 = CoverageData() - covdata2.read_file("arcs.dat") + covdata2 = CoverageData("arcs.dat") + covdata2.read() self.assert_arcs3_data(covdata2) def test_read_errors(self): - covdata = CoverageData() + msg = r"Couldn't .* '.*[/\\]{0}': \S+" - msg = r"Couldn't read data from '{0}': \S+" self.make_file("xyzzy.dat", "xyzzy") with self.assertRaisesRegex(CoverageException, msg.format("xyzzy.dat")): - covdata.read_file("xyzzy.dat") + covdata = CoverageData("xyzzy.dat") + covdata.read() + self.assertFalse(covdata) self.make_file("empty.dat", "") with self.assertRaisesRegex(CoverageException, msg.format("empty.dat")): - covdata.read_file("empty.dat") - - with self.assertRaisesRegex(CoverageException, msg.format("nonexistent.dat")): - covdata.read_file("nonexistent.dat") + covdata = CoverageData("empty.dat") + covdata.read() + self.assertFalse(covdata) + def test_read_json_errors(self): + self.skip_unless_data_storage_is("json") self.make_file("misleading.dat", CoverageData._GO_AWAY + " this isn't JSON") + msg = r"Couldn't .* '.*[/\\]{0}': \S+" with self.assertRaisesRegex(CoverageException, msg.format("misleading.dat")): - covdata.read_file("misleading.dat") + covdata = CoverageData("misleading.dat") + covdata.read() + self.assertFalse(covdata) - # After all that, no data should be in our CoverageData. + def test_read_sql_errors(self): + self.skip_unless_data_storage_is("sql") + with sqlite3.connect("wrong_schema.db") as con: + con.execute("create table coverage_schema (version integer)") + con.execute("insert into coverage_schema (version) values (99)") + msg = r"Couldn't .* '.*[/\\]{0}': wrong schema: 99 instead of \d+".format("wrong_schema.db") + with self.assertRaisesRegex(CoverageException, msg): + covdata = CoverageData("wrong_schema.db") + covdata.read() + self.assertFalse(covdata) + + with sqlite3.connect("no_schema.db") as con: + con.execute("create table foobar (baz text)") + msg = r"Couldn't .* '.*[/\\]{0}': \S+".format("no_schema.db") + with self.assertRaisesRegex(CoverageException, msg): + covdata = CoverageData("no_schema.db") + covdata.read() self.assertFalse(covdata) def test_debug_main(self): - covdata1 = CoverageData() + self.skip_unless_data_storage_is("json") + covdata1 = CoverageData(".coverage") covdata1.add_lines(LINES_1) - covdata1.write_file(".coverage") + covdata1.write() debug_main([]) - covdata2 = CoverageData() + covdata2 = CoverageData("arcs.dat") covdata2.add_arcs(ARCS_3) covdata2.add_file_tracers({"y.py": "magic_plugin"}) covdata2.add_run_info(version="v3.14", chunks=["z", "a"]) - covdata2.write_file("arcs.dat") + covdata2.write() - covdata3 = CoverageData() - covdata3.write_file("empty.dat") + covdata3 = CoverageData("empty.dat") + covdata3.write() debug_main(["arcs.dat", "empty.dat"]) expected = { @@ -518,27 +551,23 @@ class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest): class CoverageDataFilesTest(DataTestHelpers, CoverageTest): - """Tests of CoverageDataFiles.""" + """Tests of CoverageData file handling.""" no_files_in_temp_dir = True - def setUp(self): - super(CoverageDataFilesTest, self).setUp() - self.data_files = CoverageDataFiles() - def test_reading_missing(self): self.assert_doesnt_exist(".coverage") covdata = CoverageData() - self.data_files.read(covdata) + covdata.read() self.assert_line_counts(covdata, {}) def test_writing_and_reading(self): covdata1 = CoverageData() covdata1.add_lines(LINES_1) - self.data_files.write(covdata1) + covdata1.write() covdata2 = CoverageData() - self.data_files.read(covdata2) + covdata2.read() self.assert_line_counts(covdata2, SUMMARY_1) def test_debug_output_with_debug_option(self): @@ -547,16 +576,22 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): debug = DebugControlString(options=["dataio"]) covdata1 = CoverageData(debug=debug) covdata1.add_lines(LINES_1) - self.data_files.write(covdata1) + covdata1.write() covdata2 = CoverageData(debug=debug) - self.data_files.read(covdata2) + covdata2.read() self.assert_line_counts(covdata2, SUMMARY_1) self.assertRegex( debug.get_output(), + r"(" # JSON output: r"^Writing data to '.*\.coverage'\n" r"Reading data from '.*\.coverage'\n$" + r"|" # SQL output: + r"Erasing data file '.*\.coverage'\n" + r"Creating data file '.*\.coverage'\n" + r"Opening data file '.*\.coverage'\n$" + r")" ) def test_debug_output_without_debug_option(self): @@ -565,37 +600,37 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): debug = DebugControlString(options=[]) covdata1 = CoverageData(debug=debug) covdata1.add_lines(LINES_1) - self.data_files.write(covdata1) + covdata1.write() covdata2 = CoverageData(debug=debug) - self.data_files.read(covdata2) + covdata2.read() self.assert_line_counts(covdata2, SUMMARY_1) self.assertEqual(debug.get_output(), "") def test_explicit_suffix(self): self.assert_doesnt_exist(".coverage.SUFFIX") - covdata = CoverageData() + covdata = CoverageData(suffix='SUFFIX') covdata.add_lines(LINES_1) - self.data_files.write(covdata, suffix='SUFFIX') + covdata.write() self.assert_exists(".coverage.SUFFIX") self.assert_doesnt_exist(".coverage") def test_true_suffix(self): - self.assertEqual(glob.glob(".coverage.*"), []) + self.assert_file_count(".coverage.*", 0) # suffix=True will make a randomly named data file. - covdata1 = CoverageData() + covdata1 = CoverageData(suffix=True) covdata1.add_lines(LINES_1) - self.data_files.write(covdata1, suffix=True) + covdata1.write() self.assert_doesnt_exist(".coverage") data_files1 = glob.glob(".coverage.*") self.assertEqual(len(data_files1), 1) # Another suffix=True will choose a different name. - covdata2 = CoverageData() + covdata2 = CoverageData(suffix=True) covdata2.add_lines(LINES_1) - self.data_files.write(covdata2, suffix=True) + covdata2.write() self.assert_doesnt_exist(".coverage") data_files2 = glob.glob(".coverage.*") self.assertEqual(len(data_files2), 2) @@ -604,52 +639,50 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): self.assertTrue(all(str(os.getpid()) in fn for fn in data_files2)) def test_combining(self): - self.assert_doesnt_exist(".coverage.1") - self.assert_doesnt_exist(".coverage.2") + self.assert_file_count(".coverage.*", 0) - covdata1 = CoverageData() + covdata1 = CoverageData(suffix='1') covdata1.add_lines(LINES_1) - self.data_files.write(covdata1, suffix='1') + covdata1.write() self.assert_exists(".coverage.1") - self.assert_doesnt_exist(".coverage.2") + self.assert_file_count(".coverage.*", 1) - covdata2 = CoverageData() + covdata2 = CoverageData(suffix='2') covdata2.add_lines(LINES_2) - self.data_files.write(covdata2, suffix='2') + covdata2.write() self.assert_exists(".coverage.2") + self.assert_file_count(".coverage.*", 2) covdata3 = CoverageData() - self.data_files.combine_parallel_data(covdata3) + combine_parallel_data(covdata3) self.assert_line_counts(covdata3, SUMMARY_1_2) self.assert_measured_files(covdata3, MEASURED_FILES_1_2) - self.assert_doesnt_exist(".coverage.1") - self.assert_doesnt_exist(".coverage.2") + self.assert_file_count(".coverage.*", 0) def test_erasing(self): covdata1 = CoverageData() covdata1.add_lines(LINES_1) - self.data_files.write(covdata1) + covdata1.write() covdata1.erase() self.assert_line_counts(covdata1, {}) - self.data_files.erase() covdata2 = CoverageData() - self.data_files.read(covdata2) + covdata2.read() self.assert_line_counts(covdata2, {}) def test_erasing_parallel(self): self.make_file("datafile.1") self.make_file("datafile.2") self.make_file(".coverage") - data_files = CoverageDataFiles("datafile") - data_files.erase(parallel=True) - self.assert_doesnt_exist("datafile.1") - self.assert_doesnt_exist("datafile.2") + data = CoverageData("datafile") + data.erase(parallel=True) + self.assert_file_count("datafile.*", 0) self.assert_exists(".coverage") def read_json_data_file(self, fname): """Read a JSON data file for testing the JSON directly.""" + self.skip_unless_data_storage_is("json") with open(fname, 'r') as fdata: go_away = fdata.read(len(CoverageData._GO_AWAY)) self.assertEqual(go_away, CoverageData._GO_AWAY) @@ -659,7 +692,7 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): # Write with CoverageData, then read the JSON explicitly. covdata = CoverageData() covdata.add_lines(LINES_1) - self.data_files.write(covdata) + covdata.write() data = self.read_json_data_file(".coverage") @@ -676,7 +709,7 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): # Write with CoverageData, then read the JSON explicitly. covdata = CoverageData() covdata.add_arcs(ARCS_3) - self.data_files.write(covdata) + covdata.write() data = self.read_json_data_file(".coverage") @@ -689,19 +722,19 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): self.assertNotIn('file_tracers', data) def test_writing_to_other_file(self): - data_files = CoverageDataFiles(".otherfile") - covdata = CoverageData() + self.skipTest("This will be deleted!") # TODO + covdata = CoverageData(".otherfile") covdata.add_lines(LINES_1) - data_files.write(covdata) + covdata.write() self.assert_doesnt_exist(".coverage") self.assert_exists(".otherfile") - data_files.write(covdata, suffix="extra") + covdata.write(suffix="extra") self.assert_exists(".otherfile.extra") self.assert_doesnt_exist(".coverage") def test_combining_with_aliases(self): - covdata1 = CoverageData() + covdata1 = CoverageData(suffix='1') covdata1.add_lines({ '/home/ned/proj/src/a.py': {1: None, 2: None}, '/home/ned/proj/src/sub/b.py': {3: None}, @@ -710,20 +743,25 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): covdata1.add_file_tracers({ '/home/ned/proj/src/template.html': 'html.plugin', }) - self.data_files.write(covdata1, suffix='1') + covdata1.write() - covdata2 = CoverageData() + covdata2 = CoverageData(suffix='2') covdata2.add_lines({ r'c:\ned\test\a.py': {4: None, 5: None}, r'c:\ned\test\sub\b.py': {3: None, 6: None}, }) - self.data_files.write(covdata2, suffix='2') + covdata2.write() + + self.assert_file_count(".coverage.*", 2) covdata3 = CoverageData() aliases = PathAliases() aliases.add("/home/ned/proj/src/", "./") aliases.add(r"c:\ned\test", "./") - self.data_files.combine_parallel_data(covdata3, aliases=aliases) + combine_parallel_data(covdata3, aliases=aliases) + self.assert_file_count(".coverage.*", 0) + # covdata3 hasn't been written yet. Should this file exist or not? + #self.assert_exists(".coverage") apy = canonical_filename('./a.py') sub_bpy = canonical_filename('./sub/b.py') @@ -734,23 +772,23 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): self.assertEqual(covdata3.file_tracer(template_html), 'html.plugin') def test_combining_from_different_directories(self): - covdata1 = CoverageData() - covdata1.add_lines(LINES_1) os.makedirs('cov1') - covdata1.write_file('cov1/.coverage.1') + covdata1 = CoverageData('cov1/.coverage.1') + covdata1.add_lines(LINES_1) + covdata1.write() - covdata2 = CoverageData() - covdata2.add_lines(LINES_2) os.makedirs('cov2') - covdata2.write_file('cov2/.coverage.2') + covdata2 = CoverageData('cov2/.coverage.2') + covdata2.add_lines(LINES_2) + covdata2.write() # This data won't be included. - covdata_xxx = CoverageData() + covdata_xxx = CoverageData('.coverage.xxx') covdata_xxx.add_arcs(ARCS_3) - covdata_xxx.write_file('.coverage.xxx') + covdata_xxx.write() covdata3 = CoverageData() - self.data_files.combine_parallel_data(covdata3, data_paths=['cov1', 'cov2']) + combine_parallel_data(covdata3, data_paths=['cov1', 'cov2']) self.assert_line_counts(covdata3, SUMMARY_1_2) self.assert_measured_files(covdata3, MEASURED_FILES_1_2) @@ -759,24 +797,27 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): self.assert_exists(".coverage.xxx") def test_combining_from_files(self): - covdata1 = CoverageData() - covdata1.add_lines(LINES_1) os.makedirs('cov1') - covdata1.write_file('cov1/.coverage.1') + covdata1 = CoverageData('cov1/.coverage.1') + covdata1.add_lines(LINES_1) + covdata1.write() - covdata2 = CoverageData() - covdata2.add_lines(LINES_2) os.makedirs('cov2') - covdata2.write_file('cov2/.coverage.2') + covdata2 = CoverageData('cov2/.coverage.2') + covdata2.add_lines(LINES_2) + covdata2.write() # This data won't be included. - covdata_xxx = CoverageData() + covdata_xxx = CoverageData('.coverage.xxx') covdata_xxx.add_arcs(ARCS_3) - covdata_xxx.write_file('.coverage.xxx') - covdata_xxx.write_file('cov2/.coverage.xxx') + covdata_xxx.write() + + covdata_2xxx = CoverageData('cov2/.coverage.xxx') + covdata_2xxx.add_arcs(ARCS_3) + covdata_2xxx.write() covdata3 = CoverageData() - self.data_files.combine_parallel_data(covdata3, data_paths=['cov1', 'cov2/.coverage.2']) + combine_parallel_data(covdata3, data_paths=['cov1', 'cov2/.coverage.2']) self.assert_line_counts(covdata3, SUMMARY_1_2) self.assert_measured_files(covdata3, MEASURED_FILES_1_2) @@ -789,4 +830,4 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest): covdata = CoverageData() msg = "Couldn't combine from non-existent path 'xyzzy'" with self.assertRaisesRegex(CoverageException, msg): - self.data_files.combine_parallel_data(covdata, data_paths=['xyzzy']) + combine_parallel_data(covdata, data_paths=['xyzzy']) diff --git a/tests/test_debug.py b/tests/test_debug.py index 38f31f58..63edc84f 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests of coverage/debug.py""" @@ -10,9 +10,10 @@ import pytest import coverage from coverage.backward import StringIO from coverage.debug import filter_text, info_formatter, info_header, short_id, short_stack +from coverage.env import C_TRACER from tests.coveragetest import CoverageTest -from tests.helpers import re_lines +from tests.helpers import re_line, re_lines class InfoFormatterTest(CoverageTest): @@ -128,24 +129,29 @@ class DebugTraceTest(CoverageTest): def test_debug_callers(self): out_lines = self.f1_debug_output(["pid", "dataop", "dataio", "callers"]) print(out_lines) - # For every real message, there should be a stack - # trace with a line like "f1_debug_output : /Users/ned/coverage/tests/test_debug.py @71" + # For every real message, there should be a stack trace with a line like + # "f1_debug_output : /Users/ned/coverage/tests/test_debug.py @71" real_messages = re_lines(out_lines, r" @\d+", match=False).splitlines() frame_pattern = r"\s+f1_debug_output : .*tests[/\\]test_debug.py @\d+$" frames = re_lines(out_lines, frame_pattern).splitlines() self.assertEqual(len(real_messages), len(frames)) # The last message should be "Writing data", and the last frame should - # be write_file in data.py. - self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Writing data") + # be _write_file in data.py. last_line = out_lines.splitlines()[-1] - self.assertRegex(last_line, r"\s+write_file : .*coverage[/\\]data.py @\d+$") + from coverage.data import STORAGE + if STORAGE == "json": + self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Writing data") + self.assertRegex(last_line, r"\s+_write_file : .*coverage[/\\]data.py @\d+$") + else: + self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Adding lines") + self.assertRegex(last_line, r"\s+add_lines : .*coverage[/\\]sqldata.py @\d+$") def test_debug_config(self): out_lines = self.f1_debug_output(["config"]) labels = """ - attempted_config_files branch config_files cover_pylib data_file + attempted_config_files branch config_files_read config_file cover_pylib data_file debug exclude_list extra_css html_dir html_title ignore_errors run_include run_omit parallel partial_always_list partial_list paths precision show_missing source timid xml_output @@ -155,16 +161,17 @@ class DebugTraceTest(CoverageTest): label_pat = r"^\s*%s: " % label self.assertEqual( len(re_lines(out_lines, label_pat).splitlines()), - 1 + 1, + msg="Incorrect lines for %r" % label, ) def test_debug_sys(self): out_lines = self.f1_debug_output(["sys"]) labels = """ - version coverage cover_paths pylib_paths tracer config_files - configs_read data_path python platform implementation executable - cwd path environment command_line cover_match pylib_match + version coverage cover_paths pylib_paths tracer configs_attempted config_file + configs_read data_file python platform implementation executable + pid cwd path environment command_line cover_match pylib_match """.split() for label in labels: label_pat = r"^\s*%s: " % label @@ -174,6 +181,15 @@ class DebugTraceTest(CoverageTest): msg="Incorrect lines for %r" % label, ) + def test_debug_sys_ctracer(self): + out_lines = self.f1_debug_output(["sys"]) + tracer_line = re_line(out_lines, r"CTracer:").strip() + if C_TRACER: + expected = "CTracer: available" + else: + expected = "CTracer: unavailable" + self.assertEqual(tracer_line, expected) + def f_one(*args, **kwargs): """First of the chain of functions for testing `short_stack`.""" diff --git a/tests/test_execfile.py b/tests/test_execfile.py index 693df71a..cb835c2a 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.execfile""" diff --git a/tests/test_farm.py b/tests/test_farm.py index 1b52bc29..54eeb499 100644 --- a/tests/test_farm.py +++ b/tests/test_farm.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Run tests in the farm sub-directory. Designed for pytest.""" @@ -20,7 +20,6 @@ from tests.backtest import execfile # pylint: disable=redefined-builtin from coverage import env from coverage.backunittest import unittest -from coverage.debug import _TEST_NAME_FILE # Look for files that become tests. @@ -36,7 +35,7 @@ def test_farm(filename): # "rU" was deprecated in 3.4 -READ_MODE = "rU" if sys.version_info < (3, 4) else "r" +READ_MODE = "rU" if env.PYVERSION < (3, 4) else "r" class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase): @@ -103,12 +102,8 @@ class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase): """Here to make unittest.TestCase happy, but will never be invoked.""" raise Exception("runTest isn't used in this class!") - def __call__(self): - """Execute the test from the run.py file.""" - if _TEST_NAME_FILE: # pragma: debugging - with open(_TEST_NAME_FILE, "w") as f: - f.write(self.description.replace("/", "_")) - + def __call__(self): # pylint: disable=arguments-differ + """Execute the test from the runpy file.""" # Prepare a dictionary of globals for the run.py files to use. fns = """ copy run clean skip @@ -147,6 +142,8 @@ def noop(*args_unused, **kwargs_unused): def copy(src, dst): """Copy a directory.""" + if os.path.exists(dst): + pytest.fail('%s already exists.' % os.path.join(os.getcwd(), dst)) # pragma: only failure shutil.copytree(src, dst) @@ -177,9 +174,37 @@ def run(cmds, rundir="src", outfile=None): fout.close() +def versioned_directory(d): + """Find a subdirectory of d specific to the Python version. + + For example, on Python 3.6.4 rc 1, it returns the first of these + directories that exists:: + + d/3.6.4.candidate.1 + d/3.6.4.candidate + d/3.6.4 + d/3.6 + d/3 + d + + Returns: a string, the path to an existing directory. + + """ + ver_parts = list(map(str, sys.version_info)) + for nparts in range(len(ver_parts), -1, -1): + version = ".".join(ver_parts[:nparts]) + subdir = os.path.join(d, version) + if os.path.exists(subdir): + return subdir + raise Exception("Directory missing: {}".format(d)) # pragma: only failure + + def compare(dir1, dir2, file_pattern=None, size_within=0, left_extra=False, scrubs=None): """Compare files matching `file_pattern` in `dir1` and `dir2`. + A version-specific subdirectory of `dir1` or `dir2` will be used if + it exists. + `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. @@ -189,15 +214,15 @@ def compare(dir1, dir2, file_pattern=None, size_within=0, left_extra=False, scru `left_extra` true means the left directory can have extra files in it without triggering an assertion. - `scrubs` is a list of pairs, regexes to find and literal strings to - replace them with to scrub the files of unimportant differences. + `scrubs` is a list of pairs, regexes to find and replace to scrub the + files of unimportant differences. An assertion will be raised if the directories fail one of their matches. """ - assert os.path.exists(dir1), "Left directory missing: %s" % dir1 - assert os.path.exists(dir2), "Right directory missing: %s" % dir2 + dir1 = versioned_directory(dir1) + dir2 = versioned_directory(dir2) dc = filecmp.dircmp(dir1, dir2) diff_files = fnmatch_list(dc.diff_files, file_pattern) @@ -234,19 +259,23 @@ def compare(dir1, dir2, file_pattern=None, size_within=0, left_extra=False, scru # ourselves. text_diff = [] for f in diff_files: - with open(os.path.join(dir1, f), READ_MODE) as fobj: + left_file = os.path.join(dir1, f) + right_file = os.path.join(dir2, f) + with open(left_file, READ_MODE) as fobj: left = fobj.read() - with open(os.path.join(dir2, f), READ_MODE) as fobj: + with open(right_file, READ_MODE) as fobj: right = fobj.read() if scrubs: left = scrub(left, scrubs) right = scrub(right, scrubs) if left != right: # pragma: only failure - text_diff.append(f) + text_diff.append('%s != %s' % (left_file, right_file)) left = left.splitlines() right = right.splitlines() + print(":::: diff {!r} and {!r}".format(left_file, right_file)) print("\n".join(difflib.Differ().compare(left, right))) - assert not text_diff, "Files differ: %s" % text_diff + print(":::: end diff {!r} and {!r}".format(left_file, right_file)) + assert not text_diff, "Files differ: %s" % '\n'.join(text_diff) if not left_extra: assert not left_only, "Files in %s only: %s" % (dir1, left_only) @@ -344,7 +373,7 @@ def scrub(strdata, scrubs): """ for rgx_find, rgx_replace in scrubs: - strdata = re.sub(rgx_find, rgx_replace.replace("\\", "\\\\"), strdata) + strdata = re.sub(rgx_find, rgx_replace, strdata) return strdata diff --git a/tests/test_filereporter.py b/tests/test_filereporter.py index 91e47762..e50a7493 100644 --- a/tests/test_filereporter.py +++ b/tests/test_filereporter.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for FileReporters""" diff --git a/tests/test_files.py b/tests/test_files.py index dd88b6eb..b4490ea6 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for files.py""" @@ -12,7 +12,7 @@ import pytest from coverage import files from coverage.files import ( TreeMatcher, FnmatchMatcher, ModuleMatcher, PathAliases, - find_python_files, abs_file, actual_path, flat_rootname, + find_python_files, abs_file, actual_path, flat_rootname, fnmatches_to_regex, ) from coverage.misc import CoverageException from coverage import env @@ -77,6 +77,55 @@ def test_flat_rootname(original, flat): assert flat_rootname(original) == flat +@pytest.mark.parametrize( + "patterns, case_insensitive, partial," + "matches," + "nomatches", +[ + ( + ["abc", "xyz"], False, False, + ["abc", "xyz"], + ["ABC", "xYz", "abcx", "xabc", "axyz", "xyza"], + ), + ( + ["abc", "xyz"], True, False, + ["abc", "xyz", "Abc", "XYZ", "AbC"], + ["abcx", "xabc", "axyz", "xyza"], + ), + ( + ["abc/hi.py"], True, False, + ["abc/hi.py", "ABC/hi.py", r"ABC\hi.py"], + ["abc_hi.py", "abc/hi.pyc"], + ), + ( + [r"abc\hi.py"], True, False, + [r"abc\hi.py", r"ABC\hi.py"], + ["abc/hi.py", "ABC/hi.py", "abc_hi.py", "abc/hi.pyc"], + ), + ( + ["abc/*/hi.py"], True, False, + ["abc/foo/hi.py", "ABC/foo/bar/hi.py", r"ABC\foo/bar/hi.py"], + ["abc/hi.py", "abc/hi.pyc"], + ), + ( + ["abc/[a-f]*/hi.py"], True, False, + ["abc/foo/hi.py", "ABC/foo/bar/hi.py", r"ABC\foo/bar/hi.py"], + ["abc/zoo/hi.py", "abc/hi.py", "abc/hi.pyc"], + ), + ( + ["abc/"], True, True, + ["abc/foo/hi.py", "ABC/foo/bar/hi.py", r"ABC\foo/bar/hi.py"], + ["abcd/foo.py", "xabc/hi.py"], + ), +]) +def test_fnmatches_to_regex(patterns, case_insensitive, partial, matches, nomatches): + regex = fnmatches_to_regex(patterns, case_insensitive=case_insensitive, partial=partial) + for s in matches: + assert regex.match(s) + for s in nomatches: + assert not regex.match(s) + + class MatcherTest(CoverageTest): """Tests of file matchers.""" diff --git a/tests/test_html.py b/tests/test_html.py index 9bb8f392..955a8537 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests that HTML generation is awesome.""" @@ -13,12 +13,14 @@ import re import sys import coverage -import coverage.files +from coverage.backward import unicode_class +from coverage import env +from coverage.files import flat_rootname import coverage.html from coverage.misc import CoverageException, NotPython, NoSource -from tests.coveragetest import CoverageTest -from tests.goldtest import CoverageGoldTest +from tests.coveragetest import CoverageTest, TESTS_DIR +from tests.goldtest import gold_path from tests.goldtest import change_dir, compare, contains, doesnt_contain, contains_any @@ -104,6 +106,12 @@ class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): self.real_coverage_version = coverage.__version__ self.addCleanup(setattr, coverage, "__version__", self.real_coverage_version) + def run_coverage(self, covargs=None, htmlargs=None): + """For the delta tests, we always want source=. """ + covargs = covargs or {} + covargs['source'] = "." + super(HtmlDeltaTest, self).run_coverage(covargs=covargs, htmlargs=htmlargs) + def test_html_created(self): # Test basic HTML generation: files should be created. self.create_initial_files() @@ -563,26 +571,62 @@ class HtmlStaticFileTest(CoverageTest): with self.assertRaisesRegex(CoverageException, msg): cov.html_report() - -class HtmlGoldTests(CoverageGoldTest): +def filepath_to_regex(path): + """Create a regex for scrubbing a file path.""" + regex = re.escape(path) + # If there's a backslash, let it match either slash. + regex = regex.replace(r"\\", r"[\\/]") + if env.WINDOWS: + regex = "(?i)" + regex + return regex + + +def compare_html(dir1, dir2): + """Specialized compare function for HTML files.""" + scrubs = [ + (r'/coverage.readthedocs.io/?[-.\w/]*', '/coverage.readthedocs.io/VER'), + (r'coverage.py v[\d.abc]+', 'coverage.py vVER'), + (r'created at \d\d\d\d-\d\d-\d\d \d\d:\d\d', 'created at DATE'), + # Some words are identifiers in one version, keywords in another. + (r'<span class="(nam|key)">(print|True|False)</span>', r'<span class="nam">\2</span>'), + # Occasionally an absolute path is in the HTML report. + (filepath_to_regex(TESTS_DIR), 'TESTS_DIR'), + (r'/Users/ned/coverage/trunk/tests', 'TESTS_DIR'), + (filepath_to_regex(flat_rootname(unicode_class(TESTS_DIR))), '_TESTS_DIR'), + (flat_rootname(u'/Users/ned/coverage/trunk/tests'), '_TESTS_DIR'), + # The temp dir the tests make. + (filepath_to_regex(os.getcwd()), 'TEST_TMPDIR'), + (filepath_to_regex(flat_rootname(unicode_class(os.getcwd()))), '_TEST_TMPDIR'), + (r'/private/var/folders/[\w/]{35}/coverage_test/tests_test_html_\w+_\d{8}', 'TEST_TMPDIR'), + (r'_private_var_folders_\w{35}_coverage_test_tests_test_html_\w+_\d{8}', '_TEST_TMPDIR'), + ] + if env.WINDOWS: + # For file paths... + scrubs += [(r"\\", "/")] + return compare(dir1, dir2, file_pattern="*.html", scrubs=scrubs) + + +class HtmlGoldTests(CoverageTest): """Tests of HTML reporting that use gold files.""" - root_dir = 'tests/farm/html' - def test_a(self): - self.output_dir("out/a") + self.make_file("a.py", """\ + if 1 < 2: + # Needed a < to look at HTML entities. + a = 3 + else: + a = 4 + """) - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage() - cov.start() - import a # pragma: nested - cov.stop() # pragma: nested - cov.html_report(a, directory='../out/a') + cov = coverage.Coverage() + cov.start() + import a # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.html_report(a, directory='out') - compare("gold_a", "out/a", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_a")) contains( - "out/a/a_py.html", + "out/a_py.html", ('<span class="key">if</span> <span class="num">1</span> ' '<span class="op"><</span> <span class="num">2</span>'), (' <span class="nam">a</span> ' @@ -590,220 +634,345 @@ class HtmlGoldTests(CoverageGoldTest): '<span class="pc_cov">67%</span>', ) contains( - "out/a/index.html", + "out/index.html", '<a href="a_py.html">a.py</a>', '<span class="pc_cov">67%</span>', '<td class="right" data-ratio="2 3">67%</td>', ) def test_b_branch(self): - self.output_dir("out/b_branch") + self.make_file("b.py", """\ + 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() + """) - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(branch=True) - cov.start() - import b # pragma: nested - cov.stop() # pragma: nested - cov.html_report(b, directory="../out/b_branch") + cov = coverage.Coverage(branch=True) + cov.start() + import b # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.html_report(b, directory="out") - compare("gold_b_branch", "out/b_branch", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_b_branch")) contains( - "out/b_branch/b_py.html", + "out/b_py.html", ('<span class="key">if</span> <span class="nam">x</span> ' '<span class="op"><</span> <span class="num">2</span>'), (' <span class="nam">a</span> <span class="op">=</span> ' '<span class="num">3</span>'), '<span class="pc_cov">70%</span>', - ('<span class="annotate short">8 ↛ 11</span>' - '<span class="annotate long">line 8 didn\'t jump to line 11, ' - 'because the condition on line 8 was never false</span>'), - ('<span class="annotate short">17 ↛ exit</span>' - '<span class="annotate long">line 17 didn\'t return from function \'two\', ' - 'because the condition on line 17 was never false</span>'), - ('<span class="annotate short">25 ↛ 26, ' - '25 ↛ 28</span>' + + ('<span class="annotate short">3 ↛ 6</span>' + '<span class="annotate long">line 3 didn\'t jump to line 6, ' + 'because the condition on line 3 was never false</span>'), + ('<span class="annotate short">12 ↛ exit</span>' + '<span class="annotate long">line 12 didn\'t return from function \'two\', ' + 'because the condition on line 12 was never false</span>'), + ('<span class="annotate short">20 ↛ 21, ' + '20 ↛ 23</span>' '<span class="annotate long">2 missed branches: ' - '1) line 25 didn\'t jump to line 26, ' - 'because the condition on line 25 was never true, ' - '2) line 25 didn\'t jump to line 28, ' - 'because the condition on line 25 was never false</span>'), + '1) line 20 didn\'t jump to line 21, ' + 'because the condition on line 20 was never true, ' + '2) line 20 didn\'t jump to line 23, ' + 'because the condition on line 20 was never false</span>'), ) contains( - "out/b_branch/index.html", + "out/index.html", '<a href="b_py.html">b.py</a>', '<span class="pc_cov">70%</span>', '<td class="right" data-ratio="16 23">70%</td>', ) def test_bom(self): - self.output_dir("out/bom") + self.make_file("bom.py", bytes=b"""\ +\xef\xbb\xbf# A Python source file in utf-8, with BOM. +math = "3\xc3\x974 = 12, \xc3\xb72 = 6\xc2\xb10" - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage() - cov.start() - import bom # pragma: nested - cov.stop() # pragma: nested - cov.html_report(bom, directory="../out/bom") +import sys + +if sys.version_info >= (3, 0): + assert len(math) == 18 + assert len(math.encode('utf-8')) == 21 +else: + assert len(math) == 21 + assert len(math.decode('utf-8')) == 18 +""".replace(b"\n", b"\r\n")) + + # It's important that the source file really have a BOM, which can + # get lost, so check that it's really there, and that we have \r\n + # line endings. + with open("bom.py", "rb") as f: + data = f.read() + assert data[:3] == b"\xef\xbb\xbf" + assert data.count(b"\r\n") == 11 + + cov = coverage.Coverage() + cov.start() + import bom # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.html_report(bom, directory="out") - compare("gold_bom", "out/bom", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_bom")) contains( - "out/bom/bom_py.html", + "out/bom_py.html", '<span class="str">"3×4 = 12, ÷2 = 6±0"</span>', ) def test_isolatin1(self): - self.output_dir("out/isolatin1") + self.make_file("isolatin1.py", bytes=b"""\ +# -*- coding: iso8859-1 -*- +# A Python source file in another encoding. - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage() - cov.start() - import isolatin1 # pragma: nested - cov.stop() # pragma: nested - cov.html_report(isolatin1, directory="../out/isolatin1") +math = "3\xd74 = 12, \xf72 = 6\xb10" +assert len(math) == 18 +""") - compare("gold_isolatin1", "out/isolatin1", size_within=10, file_pattern="*.html") + cov = coverage.Coverage() + cov.start() + import isolatin1 # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.html_report(isolatin1, directory="out") + + compare_html("out", gold_path("html/gold_isolatin1")) contains( - "out/isolatin1/isolatin1_py.html", + "out/isolatin1_py.html", '<span class="str">"3×4 = 12, ÷2 = 6±0"</span>', ) + def make_main_etc(self): + self.make_file("main.py", """\ + import m1 + import m2 + import m3 + + a = 5 + b = 6 + + assert m1.m1a == 1 + assert m2.m2a == 1 + assert m3.m3a == 1 + """) + self.make_file("m1.py", """\ + m1a = 1 + m1b = 2 + """) + self.make_file("m2.py", """\ + m2a = 1 + m2b = 2 + """) + self.make_file("m3.py", """\ + m3a = 1 + m3b = 2 + """) + def test_omit_1(self): - self.output_dir("out/omit_1") + self.make_main_etc() - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(include=["./*"]) - cov.start() - import main # pragma: nested # pylint: disable=unused-variable - cov.stop() # pragma: nested - cov.html_report(directory="../out/omit_1") + cov = coverage.Coverage(include=["./*"]) + cov.start() + import main # pragma: nested # pylint: disable=unused-variable, import-error + cov.stop() # pragma: nested + cov.html_report(directory="out") - compare("gold_omit_1", "out/omit_1", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_omit_1")) def test_omit_2(self): - self.output_dir("out/omit_2") + self.make_main_etc() - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(include=["./*"]) - cov.start() - import main # pragma: nested # pylint: disable=unused-variable - cov.stop() # pragma: nested - cov.html_report(directory="../out/omit_2", omit=["m1.py"]) + cov = coverage.Coverage(include=["./*"]) + cov.start() + import main # pragma: nested # pylint: disable=unused-variable, import-error + cov.stop() # pragma: nested + cov.html_report(directory="out", omit=["m1.py"]) - compare("gold_omit_2", "out/omit_2", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_omit_2")) def test_omit_3(self): - self.output_dir("out/omit_3") + self.make_main_etc() - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(include=["./*"]) - cov.start() - import main # pragma: nested # pylint: disable=unused-variable - cov.stop() # pragma: nested - cov.html_report(directory="../out/omit_3", omit=["m1.py", "m2.py"]) + cov = coverage.Coverage(include=["./*"]) + cov.start() + import main # pragma: nested # pylint: disable=unused-variable, import-error + cov.stop() # pragma: nested + cov.html_report(directory="out", omit=["m1.py", "m2.py"]) - compare("gold_omit_3", "out/omit_3", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_omit_3")) def test_omit_4(self): - self.output_dir("out/omit_4") + self.make_main_etc() + self.make_file("omit4.ini", """\ + [report] + omit = m2.py + """) - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(config_file="omit4.ini", include=["./*"]) - cov.start() - import main # pragma: nested # pylint: disable=unused-variable - cov.stop() # pragma: nested - cov.html_report(directory="../out/omit_4") + cov = coverage.Coverage(config_file="omit4.ini", include=["./*"]) + cov.start() + import main # pragma: nested # pylint: disable=unused-variable, import-error + cov.stop() # pragma: nested + cov.html_report(directory="out") - compare("gold_omit_4", "out/omit_4", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_omit_4")) def test_omit_5(self): - self.output_dir("out/omit_5") + self.make_main_etc() + self.make_file("omit5.ini", """\ + [report] + omit = + fooey + gooey, m[23]*, kablooey + helloworld + + [html] + directory = out/omit_5 + """) - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(config_file="omit5.ini", include=["./*"]) - cov.start() - import main # pragma: nested # pylint: disable=unused-variable - cov.stop() # pragma: nested - cov.html_report() + cov = coverage.Coverage(config_file="omit5.ini", include=["./*"]) + cov.start() + import main # pragma: nested # pylint: disable=unused-variable, import-error + cov.stop() # pragma: nested + cov.html_report() - compare("gold_omit_5", "out/omit_5", size_within=10, file_pattern="*.html") + compare_html("out/omit_5", gold_path("html/gold_omit_5")) def test_other(self): - self.output_dir("out/other") + self.make_file("src/here.py", """\ + import other + + if 1 < 2: + h = 3 + else: + h = 4 + """) + self.make_file("othersrc/other.py", """\ + # A file in another directory. We're checking that it ends up in the + # HTML report. + + print("This is the other src!") + """) with change_dir("src"): - # pylint: disable=import-error + sys.path.insert(0, "") # pytest sometimes has this, sometimes not!? sys.path.insert(0, "../othersrc") cov = coverage.Coverage(include=["./*", "../othersrc/*"]) cov.start() - import here # pragma: nested # pylint: disable=unused-variable + import here # pragma: nested # pylint: disable=unused-variable, import-error cov.stop() # pragma: nested - cov.html_report(directory="../out/other") + cov.html_report(directory="../out") # Different platforms will name the "other" file differently. Rename it - for p in glob.glob("out/other/*_other_py.html"): - os.rename(p, "out/other/blah_blah_other_py.html") + for p in glob.glob("out/*_other_py.html"): + os.rename(p, "out/blah_blah_other_py.html") - compare("gold_other", "out/other", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_other")) contains( - "out/other/index.html", + "out/index.html", '<a href="here_py.html">here.py</a>', 'other_py.html">', 'other.py</a>', ) def test_partial(self): - self.output_dir("out/partial") + self.make_file("partial.py", """\ + # partial branches and excluded lines + a = 6 - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(config_file="partial.ini") - cov.start() - import partial # pragma: nested - cov.stop() # pragma: nested - cov.html_report(partial, directory="../out/partial") + while True: + break + + while 1: + break + + while a: # pragma: no branch + break + + if 0: + never_happen() + + if 1: + a = 21 - compare("gold_partial", "out/partial", size_within=10, file_pattern="*.html") + if a == 23: + raise AssertionError("Can't") + """) + self.make_file("partial.ini", """\ + [run] + branch = True + + [report] + exclude_lines = + raise AssertionError + """) + + cov = coverage.Coverage(config_file="partial.ini") + cov.start() + import partial # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.html_report(partial, directory="out") + + compare_html("out", gold_path("html/gold_partial")) contains( - "out/partial/partial_py.html", - '<p id="t8" class="stm run hide_run">', - '<p id="t11" class="stm run hide_run">', - '<p id="t14" class="stm run hide_run">', + "out/partial_py.html", + '<p id="t4" class="stm run hide_run">', + '<p id="t7" class="stm run hide_run">', + '<p id="t10" class="stm run hide_run">', # The "if 0" and "if 1" statements are optimized away. - '<p id="t17" class="pln">', + '<p id="t13" class="pln">', # The "raise AssertionError" is excluded by regex in the .ini. - '<p id="t24" class="exc">', + '<p id="t20" class="exc">', ) contains( - "out/partial/index.html", + "out/index.html", '<a href="partial_py.html">partial.py</a>', ) contains( - "out/partial/index.html", + "out/index.html", '<span class="pc_cov">100%</span>' ) def test_styled(self): - self.output_dir("out/styled") + self.make_file("a.py", """\ + if 1 < 2: + # Needed a < to look at HTML entities. + a = 3 + else: + a = 4 + """) - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage() - cov.start() - import a # pragma: nested - cov.stop() # pragma: nested - cov.html_report(a, directory="../out/styled", extra_css="extra.css") + self.make_file("extra.css", "/* Doesn't matter what goes in here, it gets copied. */") + + cov = coverage.Coverage() + cov.start() + import a # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.html_report(a, directory="out", extra_css="extra.css") - compare("gold_styled", "out/styled", size_within=10, file_pattern="*.html") - compare("gold_styled", "out/styled", size_within=10, file_pattern="*.css") + compare_html("out", gold_path("html/gold_styled")) + compare("out", gold_path("html/gold_styled"), size_within=10, file_pattern="*.css") contains( - "out/styled/a_py.html", + "out/a_py.html", '<link rel="stylesheet" href="extra.css" type="text/css">', ('<span class="key">if</span> <span class="num">1</span> ' '<span class="op"><</span> <span class="num">2</span>'), @@ -812,55 +981,72 @@ class HtmlGoldTests(CoverageGoldTest): '<span class="pc_cov">67%</span>' ) contains( - "out/styled/index.html", + "out/index.html", '<link rel="stylesheet" href="extra.css" type="text/css">', '<a href="a_py.html">a.py</a>', '<span class="pc_cov">67%</span>' ) def test_tabbed(self): - self.output_dir("out/tabbed") + # The file contents would look like this with 8-space tabs: + # x = 1 + # if x: + # a = "tabbed" # aligned comments + # if x: # look nice + # b = "no spaces" # when they + # c = "done" # line up. + self.make_file("tabbed.py", """\ + x = 1 + if x: + \ta = "Tabbed"\t\t\t\t# Aligned comments + \tif x:\t\t\t\t\t# look nice + \t\tb = "No spaces"\t\t\t# when they + \tc = "Done"\t\t\t\t# line up. + """) - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage() - cov.start() - import tabbed # pragma: nested - cov.stop() # pragma: nested - cov.html_report(tabbed, directory="../out/tabbed") + cov = coverage.Coverage() + cov.start() + import tabbed # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.html_report(tabbed, directory="out") # 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("tabbed.py", "\tif x:\t\t\t\t\t# look nice") contains( - "out/tabbed/tabbed_py.html", + "out/tabbed_py.html", '> <span class="key">if</span> ' '<span class="nam">x</span><span class="op">:</span>' ' ' '<span class="com"># look nice</span>' ) - doesnt_contain("out/tabbed/tabbed_py.html", "\t") + doesnt_contain("out/tabbed_py.html", "\t") def test_unicode(self): - self.output_dir("out/unicode") + self.make_file("unicode.py", """\ + # -*- coding: utf-8 -*- + # A Python source file with exotic characters. - with change_dir("src"): - # pylint: disable=import-error, redefined-builtin - cov = coverage.Coverage() - cov.start() - import unicode # pragma: nested - cov.stop() # pragma: nested - cov.html_report(unicode, directory="../out/unicode") + upside_down = "ʎd˙ǝbɐɹǝʌoɔ" + surrogate = "db40,dd00: x󠄀" + """) + + # pylint: disable=import-error, redefined-builtin + cov = coverage.Coverage() + cov.start() + import unicode # pragma: nested + cov.stop() # pragma: nested + cov.html_report(unicode, directory="out") - compare("gold_unicode", "out/unicode", size_within=10, file_pattern="*.html") + compare_html("out", gold_path("html/gold_unicode")) contains( - "out/unicode/unicode_py.html", + "out/unicode_py.html", '<span class="str">"ʎd˙ǝbɐɹǝʌoɔ"</span>', ) contains_any( - "out/unicode/unicode_py.html", + "out/unicode_py.html", '<span class="str">"db40,dd00: x��"</span>', '<span class="str">"db40,dd00: x󠄀"</span>', ) diff --git a/tests/test_misc.py b/tests/test_misc.py index 939b1c98..c8c2c9e4 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,12 +1,13 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests of miscellaneous stuff.""" import pytest from coverage.misc import contract, dummy_decorator_with_args, file_be_gone -from coverage.misc import format_lines, Hasher, one_of +from coverage.misc import format_lines, Hasher, one_of, substitute_variables +from coverage.misc import CoverageException from tests.coveragetest import CoverageTest @@ -47,6 +48,13 @@ class HasherTest(CoverageTest): h2.update({'b': 23, 'a': 17}) self.assertEqual(h1.hexdigest(), h2.hexdigest()) + def test_dict_collision(self): + h1 = Hasher() + h1.update({'a': 17, 'b': {'c': 1, 'd': 2}}) + h2 = Hasher() + h2.update({'a': 17, 'b': {'c': 1}, 'd': 2}) + self.assertNotEqual(h1.hexdigest(), h2.hexdigest()) + class RemoveFileTest(CoverageTest): """Tests of misc.file_be_gone.""" @@ -128,3 +136,32 @@ class ContractTest(CoverageTest): ]) def test_format_lines(statements, lines, result): assert format_lines(statements, lines) == result + + +VARS = { + 'FOO': 'fooey', + 'BAR': 'xyzzy', +} + +@pytest.mark.parametrize("before, after", [ + ("Nothing to do", "Nothing to do"), + ("Dollar: $$", "Dollar: $"), + ("Simple: $FOO is fooey", "Simple: fooey is fooey"), + ("Braced: X${FOO}X.", "Braced: XfooeyX."), + ("Missing: x${NOTHING}y is xy", "Missing: xy is xy"), + ("Multiple: $$ $FOO $BAR ${FOO}", "Multiple: $ fooey xyzzy fooey"), + ("Ill-formed: ${%5} ${{HI}} ${", "Ill-formed: ${%5} ${{HI}} ${"), + ("Strict: ${FOO?} is there", "Strict: fooey is there"), + ("Defaulted: ${WUT-missing}!", "Defaulted: missing!"), +]) +def test_substitute_variables(before, after): + assert substitute_variables(before, VARS) == after + +@pytest.mark.parametrize("text", [ + "Strict: ${NOTHING?} is an error", +]) +def test_substitute_variables_errors(text): + with pytest.raises(CoverageException) as exc_info: + substitute_variables(text, VARS) + assert text in str(exc_info.value) + assert "Variable NOTHING is undefined" in str(exc_info.value) diff --git a/tests/test_oddball.py b/tests/test_oddball.py index aa2f333c..5d615c35 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Oddball cases for testing coverage.py""" @@ -118,7 +118,7 @@ class RecursionTest(CoverageTest): cov = coverage.Coverage() self.start_import_stop(cov, "recur") - pytrace = (cov.collector.tracer_name() == "PyTracer") + pytrace = (cov._collector.tracer_name() == "PyTracer") expected_missing = [3] if pytrace: # pragma: no metacov expected_missing += [9, 10, 11] @@ -398,15 +398,6 @@ class ExceptionTest(CoverageTest): class DoctestTest(CoverageTest): """Tests invoked with doctest should measure properly.""" - def setUp(self): - super(DoctestTest, self).setUp() - - # This test case exists because Python 2.4's doctest module didn't play - # well with coverage. Nose fixes the problem by monkeypatching doctest. - # I want to be sure there's no monkeypatch and that I'm getting the - # doctest module that users of coverage will get. - assert 'doctest' not in sys.modules - def test_doctest(self): self.check_coverage('''\ def return_arg_or_void(arg): diff --git a/tests/test_parser.py b/tests/test_parser.py index afb87716..c2d70ee5 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.py's code parsing.""" @@ -165,11 +165,7 @@ class PythonParserTest(CoverageTest): def func(x=25): return 26 """) - if env.PYVERSION < (3, 7): - raw_statements = set([3, 4, 5, 6, 8, 9, 10, 13, 15, 16, 17, 20, 22, 23, 25, 26]) - else: - # Python 3.7 no longer includes class docstrings in the lnotab table. - raw_statements = set([3, 4, 5, 6, 8, 10, 13, 15, 16, 17, 20, 22, 23, 25, 26]) + raw_statements = set([3, 4, 5, 6, 8, 9, 10, 13, 15, 16, 17, 20, 22, 23, 25, 26]) self.assertEqual(parser.raw_statements, raw_statements) self.assertEqual(parser.statements, set([8])) @@ -201,8 +197,14 @@ class PythonParserTest(CoverageTest): pass """) self.assertEqual(parser.statements, set([1, 2, 4, 8, 10])) - self.assertEqual(parser.arcs(), set(self.arcz_to_arcs(".1 14 48 8. .2 2. -8A A-8"))) - self.assertEqual(parser.exit_counts(), {1: 1, 2: 1, 4: 1, 8: 1, 10: 1}) + expected_arcs = set(self.arcz_to_arcs(".1 14 48 8. .2 2. -8A A-8")) + expected_exits = {1: 1, 2: 1, 4: 1, 8: 1, 10: 1} + if env.PYVERSION >= (3, 7, 0, 'beta', 5): + # 3.7 changed how functions with only docstrings are numbered. + expected_arcs.update(set(self.arcz_to_arcs("-46 6-4"))) + expected_exits.update({6: 1}) + self.assertEqual(parser.arcs(), expected_arcs) + self.assertEqual(parser.exit_counts(), expected_exits) class ParserMissingArcDescriptionTest(CoverageTest): @@ -260,10 +262,6 @@ class ParserMissingArcDescriptionTest(CoverageTest): ) def test_missing_arc_descriptions_for_small_callables(self): - # We use 2.7 features here, so just skip this test on 2.6 - if env.PYVERSION < (2, 7): - self.skipTest("No dict or set comps in 2.6") - parser = self.parse_text(u"""\ callables = [ lambda: 2, diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index 15bc6af0..1045225e 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.py's improved tokenizer.""" diff --git a/tests/test_pickle2json.py b/tests/test_pickle2json.py deleted file mode 100644 index 433dade6..00000000 --- a/tests/test_pickle2json.py +++ /dev/null @@ -1,55 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -"""Tests for coverage.pickle2json""" - -from coverage.backward import pickle, iitems -from coverage.data import CoverageData -from coverage.pickle2json import pickle2json - -from tests.coveragetest import CoverageTest -from tests.test_data import DataTestHelpers, LINES_1, ARCS_3 - - -class Pickle2JsonTestInTempDir(DataTestHelpers, CoverageTest): - """Tests pickle2json.py.""" - - no_files_in_temp_dir = True - - def write_pickled_file(self, covdata, filename): - """Write coverage data as pickled `filename`.""" - # Create the file data. - file_data = {} - - if covdata._arcs: - file_data['arcs'] = dict((f, list(amap)) for f, amap in iitems(covdata._arcs)) - else: - file_data['lines'] = dict((f, list(lmap)) for f, lmap in iitems(covdata._lines)) - - # Write the pickle to the file. - with open(filename, 'wb') as file_obj: - pickle.dump(file_data, file_obj, 2) - - def test_read_write_lines_pickle(self): - # Test the old pickle format. - covdata1 = CoverageData() - covdata1.add_lines(LINES_1) - self.write_pickled_file(covdata1, "lines.pkl") - - pickle2json("lines.pkl", "lines.json") - - covdata2 = CoverageData() - covdata2.read_file("lines.json") - self.assert_lines1_data(covdata2) - - def test_read_write_arcs_pickle(self): - # Test the old pickle format. - covdata1 = CoverageData() - covdata1.add_arcs(ARCS_3) - self.write_pickled_file(covdata1, "arcs.pkl") - - pickle2json("arcs.pkl", "arcs.json") - - covdata2 = CoverageData() - covdata2.read_file("arcs.json") - self.assert_arcs3_data(covdata2) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 27227c33..b1614832 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for plugins.""" @@ -8,6 +8,7 @@ import os.path import coverage from coverage import env from coverage.backward import StringIO +from coverage.data import line_counts from coverage.control import Plugins from coverage.misc import CoverageException @@ -189,7 +190,8 @@ class PluginTest(CoverageTest): cov = coverage.Coverage(debug=["sys"]) cov._debug_file = debug_out cov.set_option("run:plugins", ["plugin_sys_info"]) - cov.load() + cov.start() + cov.stop() out_lines = [line.strip() for line in debug_out.getvalue().splitlines()] if env.C_TRACER: @@ -218,7 +220,8 @@ class PluginTest(CoverageTest): cov = coverage.Coverage(debug=["sys"]) cov._debug_file = debug_out cov.set_option("run:plugins", ["plugin_no_sys_info"]) - cov.load() + cov.start() + cov.stop() out_lines = [line.strip() for line in debug_out.getvalue().splitlines()] self.assertIn('plugins.file_tracers: -none-', out_lines) @@ -369,19 +372,19 @@ class GoodFileTracerTest(FileTracerTest): _, statements, missing, _ = cov.analysis("foo_7.html") self.assertEqual(statements, [1, 2, 3, 4, 5, 6, 7]) self.assertEqual(missing, [1, 2, 3, 6, 7]) - self.assertIn("foo_7.html", cov.data.line_counts()) + self.assertIn("foo_7.html", line_counts(cov.get_data())) _, statements, missing, _ = cov.analysis("bar_4.html") self.assertEqual(statements, [1, 2, 3, 4]) self.assertEqual(missing, [1, 4]) - self.assertIn("bar_4.html", cov.data.line_counts()) + self.assertIn("bar_4.html", line_counts(cov.get_data())) - self.assertNotIn("quux_5.html", cov.data.line_counts()) + self.assertNotIn("quux_5.html", line_counts(cov.get_data())) _, statements, missing, _ = cov.analysis("uni_3.html") self.assertEqual(statements, [1, 2, 3]) self.assertEqual(missing, [1]) - self.assertIn("uni_3.html", cov.data.line_counts()) + self.assertIn("uni_3.html", line_counts(cov.get_data())) def test_plugin2_with_branch(self): self.make_render_and_caller() @@ -860,8 +863,6 @@ class BadFileTracerTest(FileTracerTest): class ConfigurerPluginTest(CoverageTest): """Test configuring plugins.""" - run_in_temp_dir = False - def test_configurer_plugin(self): cov = coverage.Coverage() cov.set_option("run:plugins", ["tests.plugin_config"]) diff --git a/tests/test_process.py b/tests/test_process.py index 18564cb8..626c8094 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for process behavior of coverage.py.""" @@ -16,6 +16,7 @@ import pytest import coverage from coverage import env, CoverageData +from coverage.data import line_counts from coverage.misc import output_encoding from tests.coveragetest import CoverageTest @@ -25,14 +26,6 @@ from tests.helpers import re_lines class ProcessTest(CoverageTest): """Tests of the per-process behavior of coverage.py.""" - def data_files(self): - """Return the names of coverage data files in this directory.""" - return [f for f in os.listdir('.') if (f.startswith('.coverage.') or f == '.coverage')] - - def number_of_data_files(self): - """Return the number of coverage data files in this directory.""" - return len(self.data_files()) - def test_save_on_exit(self): self.make_file("mycode.py", """\ h = "Hello" @@ -59,6 +52,9 @@ class ProcessTest(CoverageTest): def make_b_or_c_py(self): """Create b_or_c.py, used in a few of these tests.""" + # "b_or_c.py b" will run 6 lines. + # "b_or_c.py c" will run 7 lines. + # Together, they run 8 lines. self.make_file("b_or_c.py", """\ import sys a = 1 @@ -66,6 +62,7 @@ class ProcessTest(CoverageTest): b = 1 else: c = 1 + c2 = 2 d = 1 print('done') """) @@ -75,27 +72,27 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage run -p b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_doesnt_exist(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 1) out = self.run_command("coverage run -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) + self.assert_file_count(".coverage.*", 2) # Combine the parallel coverage data files into .coverage . 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) + self.assert_file_count(".coverage.*", 0) - # Read the coverage file and see that b_or_c.py has all 7 lines + # Read the coverage file and see that b_or_c.py has all 8 lines # executed. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 7) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 8) # Running combine again should fail, because there are no parallel data # files to combine. @@ -105,22 +102,22 @@ class ProcessTest(CoverageTest): # And the originally combined data is still there. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 7) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 8) def test_combine_parallel_data_with_a_corrupt_file(self): self.make_b_or_c_py() out = self.run_command("coverage run -p b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_doesnt_exist(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 1) out = self.run_command("coverage run -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) + self.assert_file_count(".coverage.*", 2) # Make a bogus data file. self.make_file(".coverage.bad", "This isn't a coverage data file.") @@ -130,19 +127,24 @@ class ProcessTest(CoverageTest): self.assert_exists(".coverage") self.assert_exists(".coverage.bad") warning_regex = ( + r"(" # JSON message: r"Coverage.py warning: Couldn't read data from '.*\.coverage\.bad': " r"CoverageException: Doesn't seem to be a coverage\.py data file" + r"|" # SQL message: + r"Coverage.py warning: Couldn't use data file '.*\.coverage\.bad': " + r"file (is encrypted or )?is not a database" + r")" ) self.assertRegex(out, warning_regex) # After combining, those two should be the only data files. - self.assertEqual(self.number_of_data_files(), 2) + self.assert_file_count(".coverage.*", 1) - # Read the coverage file and see that b_or_c.py has all 7 lines + # Read the coverage file and see that b_or_c.py has all 8 lines # executed. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 7) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 8) def test_combine_no_usable_files(self): # https://bitbucket.org/ned/coveragepy/issues/629/multiple-use-of-combine-leads-to-empty @@ -150,7 +152,7 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage run b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_exists(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 0) # Make bogus data files. self.make_file(".coverage.bad1", "This isn't a coverage data file.") @@ -163,20 +165,27 @@ class ProcessTest(CoverageTest): for n in "12": self.assert_exists(".coverage.bad{0}".format(n)) warning_regex = ( + r"(" # JSON message: r"Coverage.py warning: Couldn't read data from '.*\.coverage\.bad{0}': " - r"CoverageException: Doesn't seem to be a coverage\.py data file".format(n) + r"CoverageException: Doesn't seem to be a coverage\.py data file" + r"|" # SQL message: + r"Coverage.py warning: Couldn't use data file '.*\.coverage.bad{0}': " + r"file (is encrypted or )?is not a database" + r")" + .format(n) ) self.assertRegex(out, warning_regex) self.assertRegex(out, r"No usable data files") # After combining, we should have a main file and two parallel files. - self.assertEqual(self.number_of_data_files(), 3) + self.assert_exists(".coverage") + self.assert_file_count(".coverage.*", 2) # Read the coverage file and see that b_or_c.py has 6 lines # executed (we only did b, not c). data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 6) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 6) def test_combine_parallel_data_in_two_steps(self): self.make_b_or_c_py() @@ -184,30 +193,62 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage run -p b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_doesnt_exist(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 1) # Combine the (one) parallel coverage data file into .coverage . self.run_command("coverage combine") self.assert_exists(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 0) out = self.run_command("coverage run -p b_or_c.py c") self.assertEqual(out, 'done\n') self.assert_exists(".coverage") - self.assertEqual(self.number_of_data_files(), 2) + self.assert_file_count(".coverage.*", 1) # Combine the parallel coverage data files into .coverage . self.run_command("coverage combine --append") self.assert_exists(".coverage") # After combining, there should be only the .coverage file. - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 0) - # Read the coverage file and see that b_or_c.py has all 7 lines + # Read the coverage file and see that b_or_c.py has all 8 lines # executed. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 7) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 8) + + def test_combine_parallel_data_no_append(self): + self.make_b_or_c_py() + + out = self.run_command("coverage run -p b_or_c.py b") + self.assertEqual(out, 'done\n') + self.assert_doesnt_exist(".coverage") + self.assert_file_count(".coverage.*", 1) + + # Combine the (one) parallel coverage data file into .coverage . + self.run_command("coverage combine") + self.assert_exists(".coverage") + self.assert_file_count(".coverage.*", 0) + + out = self.run_command("coverage run -p b_or_c.py c") + self.assertEqual(out, 'done\n') + self.assert_exists(".coverage") + self.assert_file_count(".coverage.*", 1) + + # Combine the parallel coverage data files into .coverage, but don't + # use the data in .coverage already. + self.run_command("coverage combine") + self.assert_exists(".coverage") + + # After combining, there should be only the .coverage file. + self.assert_file_count(".coverage.*", 0) + + # Read the coverage file and see that b_or_c.py has only 7 lines + # because we didn't keep the data from running b. + data = coverage.CoverageData() + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 7) def test_append_data(self): self.make_b_or_c_py() @@ -215,18 +256,18 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage run b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_exists(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 0) out = self.run_command("coverage run --append b_or_c.py c") self.assertEqual(out, 'done\n') self.assert_exists(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 0) - # Read the coverage file and see that b_or_c.py has all 7 lines + # Read the coverage file and see that b_or_c.py has all 8 lines # executed. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 7) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 8) def test_append_data_with_different_file(self): self.make_b_or_c_py() @@ -246,11 +287,11 @@ class ProcessTest(CoverageTest): self.assert_doesnt_exist(".coverage") self.assert_exists(".mycovdata") - # Read the coverage file and see that b_or_c.py has all 7 lines + # Read the coverage file and see that b_or_c.py has all 8 lines # executed. - data = coverage.CoverageData() - data.read_file(".mycovdata") - self.assertEqual(data.line_counts()['b_or_c.py'], 7) + data = coverage.CoverageData(".mycovdata") + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 8) def test_append_can_create_a_data_file(self): self.make_b_or_c_py() @@ -258,13 +299,13 @@ class ProcessTest(CoverageTest): out = self.run_command("coverage run --append b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_exists(".coverage") - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 0) # Read the coverage file and see that b_or_c.py has only 6 lines # executed. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 6) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 6) def test_combine_with_rc(self): self.make_b_or_c_py() @@ -283,7 +324,7 @@ class ProcessTest(CoverageTest): self.assert_doesnt_exist(".coverage") # After two runs, there should be two .coverage.machine.123 files. - self.assertEqual(self.number_of_data_files(), 2) + self.assert_file_count(".coverage.*", 2) # Combine the parallel coverage data files into .coverage . self.run_command("coverage combine") @@ -291,20 +332,20 @@ class ProcessTest(CoverageTest): self.assert_exists(".coveragerc") # After combining, there should be only the .coverage file. - self.assertEqual(self.number_of_data_files(), 1) + self.assert_file_count(".coverage.*", 0) - # Read the coverage file and see that b_or_c.py has all 7 lines + # Read the coverage file and see that b_or_c.py has all 8 lines # executed. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['b_or_c.py'], 7) + data.read() + self.assertEqual(line_counts(data)['b_or_c.py'], 8) # 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.py 7 0 100% + b_or_c.py 8 0 100% """)) def test_combine_with_aliases(self): @@ -339,19 +380,19 @@ class ProcessTest(CoverageTest): 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.assert_file_count(".coverage.*", 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) + self.assert_file_count(".coverage.*", 0) # 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.line_counts(fullpath=True) + data.read() + summary = line_counts(data, 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')) @@ -498,25 +539,24 @@ class ProcessTest(CoverageTest): # After running the forking program, there should be two # .coverage.machine.123 files. - self.assertEqual(self.number_of_data_files(), 2) + self.assert_file_count(".coverage.*", 2) # The two data files should have different random numbers at the end of # the file name. - nums = set(name.rpartition(".")[-1] for name in self.data_files()) - self.assertEqual(len(nums), 2, "Same random: %s" % (self.data_files(),)) + data_files = glob.glob(".coverage.*") + nums = set(name.rpartition(".")[-1] for name in data_files) + self.assertEqual(len(nums), 2, "Same random: %s" % (data_files,)) # Combine the parallel coverage data files into .coverage . 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) + self.assert_file_count(".coverage.*", 0) - # 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.line_counts()['fork.py'], 9) + data.read() + self.assertEqual(line_counts(data)['fork.py'], 9) def test_warnings_during_reporting(self): # While fixing issue #224, the warnings were being printed far too @@ -585,10 +625,36 @@ class ProcessTest(CoverageTest): self.assertIn("Trace function changed", out) + def test_warn_preimported(self): + self.make_file("hello.py", """\ + import goodbye + import coverage + cov = coverage.Coverage(include=["good*"], check_preimported=True) + cov.start() + print(goodbye.f()) + cov.stop() + """) + self.make_file("goodbye.py", """\ + def f(): + return "Goodbye!" + """) + goodbye_path = os.path.abspath("goodbye.py") + + out = self.run_command("python hello.py") + self.assertIn("Goodbye!", out) + + msg = ( + "Coverage.py warning: " + "Already imported a file that will be measured: {0} " + "(already-imported)").format(goodbye_path) + self.assertIn(msg, out) + def test_note(self): - if env.PYPY and env.PY3 and env.PYPYVERSION[:3] == (5, 10, 0): + if env.PYPY and env.PY3 and env.PYPYVERSION[:3] == (5, 10, 0): # pragma: obscure # https://bitbucket.org/pypy/pypy/issues/2729/pypy3-510-incorrectly-decodes-astral-plane self.skipTest("Avoid incorrect decoding astral plane JSON chars") + self.skip_unless_data_storage_is("json") + self.make_file(".coveragerc", """\ [run] data_file = mydata.dat @@ -597,8 +663,8 @@ class ProcessTest(CoverageTest): self.make_file("simple.py", """print('hello')""") self.run_command("coverage run simple.py") - data = CoverageData() - data.read_file("mydata.dat") + data = CoverageData("mydata.dat") + data.read() infos = data.run_infos() self.assertEqual(len(infos), 1) expected = u"These are musical notes: ♫𝅗𝅥♩" @@ -628,16 +694,13 @@ class ProcessTest(CoverageTest): out = self.run_command("python -m coverage run -L getenv.py") self.assertEqual(out, "FOOEY == BOO\n") data = coverage.CoverageData() - data.read_file(".coverage") + data.read() # 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.line_counts()['os.py'], 50) + self.assertGreater(line_counts(data)['os.py'], 50) def test_lang_c(self): - if env.PY3 and sys.version_info < (3, 4): - # Python 3.3 can't compile the non-ascii characters in the file name. - self.skipTest("3.3 can't handle this test") if env.JYTHON: # Jython as of 2.7.1rc3 won't compile a filename that isn't utf8. self.skipTest("Jython can't handle this test") @@ -666,6 +729,11 @@ class ProcessTest(CoverageTest): import coverage print("No warnings!") """) + + # Some of our testing infrastructure can issue warnings. + # Turn it all off for the sub-process. + self.del_environ("COVERAGE_TESTING") + out = self.run_command("python allok.py") self.assertEqual(out, "No warnings!\n") @@ -676,9 +744,11 @@ class ProcessTest(CoverageTest): pass """) self.make_file("run_twice.py", """\ + import sys import coverage - for _ in [1, 2]: + for i in [1, 2]: + sys.stderr.write("Run %s\\n" % i) inst = coverage.Coverage(source=['foo']) inst.load() inst.start() @@ -689,15 +759,13 @@ class ProcessTest(CoverageTest): out = self.run_command("python run_twice.py") self.assertEqual( out, + "Run 1\n" + "Run 2\n" "Coverage.py warning: Module foo was previously imported, but not measured " "(module-not-measured)\n" ) def test_module_name(self): - if sys.version_info < (2, 7): - # Python 2.6 thinks that coverage is a package that can't be - # executed - self.skipTest("-m doesn't work the same < Python 2.7") # https://bitbucket.org/ned/coveragepy/issues/478/help-shows-silly-program-name-when-running out = self.run_command("python -m coverage") self.assertIn("Use 'coverage help' for help", out) @@ -739,7 +807,7 @@ class EnvironmentTest(CoverageTest): self.assert_tryexecfile_output(out_cov, out_py) def test_coverage_run_dir_is_like_python_dir(self): - if sys.version_info == (3, 5, 4, 'final', 0): + if env.PYVERSION == (3, 5, 4, 'final', 0): # pragma: obscure self.skipTest("3.5.4 broke this: https://bugs.python.org/issue32551") with open(TRY_EXECFILE) as f: self.make_file("with_main/__main__.py", f.read()) @@ -819,10 +887,6 @@ class EnvironmentTest(CoverageTest): self.assert_tryexecfile_output(out_cov, out_py) def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self): - if sys.version_info < (2, 7): - # Coverage.py isn't bug-for-bug compatible in the behavior - # of -m for Pythons < 2.7 - self.skipTest("-m doesn't work the same < Python 2.7") # https://bitbucket.org/ned/coveragepy/issue/207 self.make_file("package/__init__.py", "print('init')") self.make_file("package/__main__.py", "print('main')") @@ -860,8 +924,8 @@ class ExcepthookTest(CoverageTest): # Read the coverage file and see that excepthook.py has 7 lines # executed. data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['excepthook.py'], 7) + data.read() + self.assertEqual(line_counts(data)['excepthook.py'], 7) def test_excepthook_exit(self): if env.PYPY or env.JYTHON: @@ -1189,9 +1253,9 @@ class ProcessStartupTest(ProcessCoverageMixin, CoverageTest): # An existing data file should not be read when a subprocess gets # measured automatically. Create the data file here with bogus data in # it. - data = coverage.CoverageData() + data = coverage.CoverageData(".mycovdata") data.add_lines({os.path.abspath('sub.py'): dict.fromkeys(range(100))}) - data.write_file(".mycovdata") + data.write() self.make_file("coverage.ini", """\ [run] @@ -1205,9 +1269,9 @@ class ProcessStartupTest(ProcessCoverageMixin, CoverageTest): # Read the data from .coverage self.assert_exists(".mycovdata") - data = coverage.CoverageData() - data.read_file(".mycovdata") - self.assertEqual(data.line_counts()['sub.py'], 3) + data = coverage.CoverageData(".mycovdata") + data.read() + self.assertEqual(line_counts(data)['sub.py'], 3) def test_subprocess_with_pth_files_and_parallel(self): # pragma: no metacov # https://bitbucket.org/ned/coveragepy/issues/492/subprocess-coverage-strange-detection-of @@ -1230,8 +1294,8 @@ class ProcessStartupTest(ProcessCoverageMixin, CoverageTest): # assert that the combined .coverage data file is correct self.assert_exists(".coverage") data = coverage.CoverageData() - data.read_file(".coverage") - self.assertEqual(data.line_counts()['sub.py'], 3) + data.read() + self.assertEqual(line_counts(data)['sub.py'], 3) # assert that there are *no* extra data files left over after a combine data_files = glob.glob(os.getcwd() + '/.coverage*') @@ -1320,8 +1384,8 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest): # Read the data from .coverage self.assert_exists(".coverage") data = coverage.CoverageData() - data.read_file(".coverage") - summary = data.line_counts() + data.read() + summary = line_counts(data) print(summary) self.assertEqual(summary[source + '.py'], 3) self.assertEqual(len(summary), 1) diff --git a/tests/test_python.py b/tests/test_python.py index 9027aa6c..441ef499 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests of coverage/python.py""" diff --git a/tests/test_results.py b/tests/test_results.py index deaf8113..307ef762 100644 --- a/tests/test_results.py +++ b/tests/test_results.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.py's results analysis.""" diff --git a/tests/test_setup.py b/tests/test_setup.py index 78fcefc9..d786ca05 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests of miscellaneous stuff.""" @@ -28,7 +28,7 @@ class SetupPyTest(CoverageTest): out = output.splitlines() self.assertIn("measurement", out[0]) self.assertEqual(coverage.__version__, out[1]) - self.assertIn("bitbucket.org/ned/coveragepy", out[2]) + self.assertIn("github.com/nedbat/coveragepy", out[2]) self.assertIn("Ned Batchelder", out[3]) def test_more_metadata(self): diff --git a/tests/test_summary.py b/tests/test_summary.py index 44defa5b..c3b572d2 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Test text-based summary reporting for coverage.py""" @@ -33,6 +33,14 @@ class SummaryTest(UsingModulesMixin, CoverageTest): a = 1 print('done') """) + self.omit_site_packages() + + def omit_site_packages(self): + """Write a .coveragerc file that will omit site-packages from reports.""" + self.make_file(".coveragerc", """\ + [report] + omit = */site-packages/* + """) def test_report(self): self.make_mycode() @@ -92,7 +100,8 @@ class SummaryTest(UsingModulesMixin, CoverageTest): # Try reporting while omitting some modules self.make_mycode() self.run_command("coverage run mycode.py") - report = self.report_from_command("coverage report --omit '%s/*'" % TESTS_DIR) + omit = '{}/*,*/site-packages/*'.format(TESTS_DIR) + report = self.report_from_command("coverage report --omit '{}'".format(omit)) # Name Stmts Miss Cover # ------------------------------- @@ -152,7 +161,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): # Read the data written, to see that the right files have been omitted from running. covdata = CoverageData() - covdata.read_file(".coverage") + covdata.read() files = [os.path.basename(p) for p in covdata.measured_files()] self.assertIn("covmod1.py", files) self.assertNotIn("covmodzip1.py", files) @@ -215,6 +224,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): print("y") branch(1, 1) """) + self.omit_site_packages() out = self.run_command("coverage run --branch mybranch.py") self.assertEqual(out, 'x\ny\n') report = self.report_from_command("coverage report --show-missing") @@ -243,6 +253,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): return x branch(1, 1, 0) """) + self.omit_site_packages() out = self.run_command("coverage run --branch main.py") self.assertEqual(out, 'x\ny\n') report = self.report_from_command("coverage report --show-missing") @@ -265,11 +276,12 @@ class SummaryTest(UsingModulesMixin, CoverageTest): def normal(): print("z") normal() - """) + """) self.make_file("not_covered.py", """ def not_covered(): print("n") - """) + """) + self.omit_site_packages() out = self.run_command("coverage run main.py") self.assertEqual(out, "z\n") report = self.report_from_command("coverage report --skip-covered --fail-under=70") @@ -298,18 +310,19 @@ class SummaryTest(UsingModulesMixin, CoverageTest): print("z") normal(True) normal(False) - """) + """) self.make_file("not_covered.py", """ def not_covered(n): if n: print("n") not_covered(True) - """) + """) self.make_file("covered.py", """ def foo(): pass foo() - """) + """) + self.omit_site_packages() out = self.run_command("coverage run --branch main.py") self.assertEqual(out, "n\nz\n") report = self.report_from_command("coverage report --skip-covered") @@ -338,17 +351,18 @@ class SummaryTest(UsingModulesMixin, CoverageTest): print("z") normal(True) normal(False) - """) + """) self.make_file("not_covered.py", """ def not_covered(n): if n: print("n") not_covered(True) - """) + """) self.make_file("also_not_run.py", """ def does_not_appear_in_this_film(ni): print("Ni!") """) + self.omit_site_packages() out = self.run_command("coverage run --branch main.py") self.assertEqual(out, "n\nz\n") report = self.report_from_command("coverage report --skip-covered") @@ -374,7 +388,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): def foo(): pass foo() - """) + """) out = self.run_command("coverage run --branch main.py") self.assertEqual(out, "") report = self.report_from_command("coverage report --skip-covered") @@ -393,7 +407,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): def foo(): pass foo() - """) + """) out = self.run_command("coverage run --branch long_______________filename.py") self.assertEqual(out, "") report = self.report_from_command("coverage report --skip-covered") @@ -424,6 +438,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): self.make_file(".coveragerc", """\ [report] precision = 3 + omit = */site-packages/* """) self.make_file("main.py", """ import not_covered, covered @@ -433,18 +448,18 @@ class SummaryTest(UsingModulesMixin, CoverageTest): print("z") normal(True) normal(False) - """) + """) self.make_file("not_covered.py", """ def not_covered(n): if n: print("n") not_covered(True) - """) + """) self.make_file("covered.py", """ def foo(): pass foo() - """) + """) out = self.run_command("coverage run --branch main.py") self.assertEqual(out, "n\nz\n") report = self.report_from_command("coverage report") @@ -680,8 +695,6 @@ class SummaryTest2(UsingModulesMixin, CoverageTest): # needs of their setUp, rather than the product features they are testing. # There's probably a better way to organize these. - run_in_temp_dir = False - def test_empty_files(self): # Shows that empty files like __init__.py are listed as having zero # statements, not one statement. @@ -737,26 +750,31 @@ class ReportingReturnValueTest(CoverageTest): class TestSummaryReporterConfiguration(CoverageTest): """Tests of SummaryReporter.""" - run_in_temp_dir = False - - LINES_1 = { - os.path.join(TESTS_DIR, "test_api.py"): dict.fromkeys(range(400)), - os.path.join(TESTS_DIR, "test_backward.py"): dict.fromkeys(range(20)), - os.path.join(TESTS_DIR, "test_coverage.py"): dict.fromkeys(range(15)), - } - - def get_coverage_data(self, lines): - """Get a CoverageData object that includes the requested lines.""" - data = CoverageData() - data.add_lines(lines) - return data - - def get_summary_text(self, coverage_data, options): + def make_rigged_file(self, filename, stmts, miss): + """Create a file that will have specific results. + + `stmts` and `miss` are ints, the number of statements, and + missed statements that should result. + """ + run = stmts - miss - 1 + dont_run = miss + source = "" + source += "a = 1\n" * run + source += "if a == 99:\n" + source += " a = 2\n" * dont_run + self.make_file(filename, source) + + def get_summary_text(self, options): """Get text output from the SummaryReporter.""" - cov = Coverage() + self.make_rigged_file("file1.py", 339, 155) + self.make_rigged_file("file2.py", 13, 3) + self.make_rigged_file("file3.py", 234, 228) + self.make_file("doit.py", "import file1, file2, file3") + + cov = Coverage(source=["."], omit=["doit.py"]) cov.start() + import doit # pragma: nested # pylint: disable=import-error, unused-variable cov.stop() # pragma: nested - cov.data = coverage_data printer = SummaryReporter(cov, options) destination = StringIO() printer.report([], destination) @@ -766,8 +784,7 @@ class TestSummaryReporterConfiguration(CoverageTest): # We use our own test files as test data. Check that our assumptions # about them are still valid. We want the three columns of numbers to # sort in three different orders. - data = self.get_coverage_data(self.LINES_1) - report = self.get_summary_text(data, CoverageConfig()) + report = self.get_summary_text(CoverageConfig()) print(report) # Name Stmts Miss Cover # -------------------------------------------- @@ -791,18 +808,16 @@ class TestSummaryReporterConfiguration(CoverageTest): def test_defaults(self): """Run the report with no configuration options.""" - data = self.get_coverage_data(self.LINES_1) opts = CoverageConfig() - report = self.get_summary_text(data, opts) + report = self.get_summary_text(opts) self.assertNotIn('Missing', report) self.assertNotIn('Branch', report) def test_print_missing(self): """Run the report printing the missing lines.""" - data = self.get_coverage_data(self.LINES_1) opts = CoverageConfig() opts.from_args(show_missing=True) - report = self.get_summary_text(data, opts) + report = self.get_summary_text(opts) self.assertIn('Missing', report) self.assertNotIn('Branch', report) @@ -816,33 +831,29 @@ class TestSummaryReporterConfiguration(CoverageTest): def test_sort_report_by_stmts(self): # Sort the text report by the Stmts column. - data = self.get_coverage_data(self.LINES_1) opts = CoverageConfig() opts.from_args(sort='Stmts') - report = self.get_summary_text(data, opts) + report = self.get_summary_text(opts) self.assert_ordering(report, "test_backward.py", "test_coverage.py", "test_api.py") def test_sort_report_by_missing(self): # Sort the text report by the Missing column. - data = self.get_coverage_data(self.LINES_1) opts = CoverageConfig() opts.from_args(sort='Miss') - report = self.get_summary_text(data, opts) + report = self.get_summary_text(opts) self.assert_ordering(report, "test_backward.py", "test_api.py", "test_coverage.py") def test_sort_report_by_cover(self): # Sort the text report by the Cover column. - data = self.get_coverage_data(self.LINES_1) opts = CoverageConfig() opts.from_args(sort='Cover') - report = self.get_summary_text(data, opts) + report = self.get_summary_text(opts) self.assert_ordering(report, "test_coverage.py", "test_api.py", "test_backward.py") def test_sort_report_by_invalid_option(self): # Sort the text report by a nonsense column. - data = self.get_coverage_data(self.LINES_1) opts = CoverageConfig() opts.from_args(sort='Xyzzy') msg = "Invalid sorting option: 'Xyzzy'" with self.assertRaisesRegex(CoverageException, msg): - self.get_summary_text(data, opts) + self.get_summary_text(opts) diff --git a/tests/test_templite.py b/tests/test_templite.py index bcc65f94..16942db8 100644 --- a/tests/test_templite.py +++ b/tests/test_templite.py @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for coverage.templite.""" diff --git a/tests/test_testing.py b/tests/test_testing.py index 05bf0c92..2b01584e 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests that our test infrastructure is really working!""" @@ -51,6 +51,23 @@ class CoverageTestTest(CoverageTest): with self.assertRaises(AssertionError): self.assert_exists("shadow.txt") + def test_file_count(self): + self.make_file("abcde.txt", "abcde") + self.make_file("axczz.txt", "axczz") + self.make_file("afile.txt", "afile") + self.assert_file_count("a*.txt", 3) + self.assert_file_count("*c*.txt", 2) + self.assert_file_count("afile.*", 1) + self.assert_file_count("*.q", 0) + with self.assertRaises(AssertionError): + self.assert_file_count("a*.txt", 13) + with self.assertRaises(AssertionError): + self.assert_file_count("*c*.txt", 12) + with self.assertRaises(AssertionError): + self.assert_file_count("afile.*", 11) + with self.assertRaises(AssertionError): + self.assert_file_count("*.q", 10) + def test_assert_startwith(self): self.assert_starts_with("xyzzy", "xy") self.assert_starts_with("xyz\nabc", "xy") diff --git a/tests/test_version.py b/tests/test_version.py index eb8de875..11b180d5 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,5 +1,5 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests of version.py.""" diff --git a/tests/test_xml.py b/tests/test_xml.py index b49debc9..f3a9e70b 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -1,6 +1,6 @@ # coding: utf-8 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Tests for XML reports from coverage.py.""" @@ -13,8 +13,7 @@ from coverage.backward import import_local_file from coverage.files import abs_file from tests.coveragetest import CoverageTest -from tests.goldtest import CoverageGoldTest -from tests.goldtest import change_dir, compare +from tests.goldtest import change_dir, compare, gold_path from tests.helpers import re_line, re_lines @@ -310,65 +309,82 @@ def clean(text, scrub=None): return text -class XmlGoldTest(CoverageGoldTest): +class XmlGoldTest(CoverageTest): """Tests of XML reporting that use gold files.""" - # TODO: this should move out of html. - root_dir = 'tests/farm/html' - def test_a_xml_1(self): - self.output_dir("out/xml_1") + self.make_file("a.py", """\ + if 1 < 2: + # Needed a < to look at HTML entities. + a = 3 + else: + a = 4 + """) - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage() - cov.start() - import a # pragma: nested - cov.stop() # pragma: nested - cov.xml_report(a, outfile="../out/xml_1/coverage.xml") - source_path = coverage.files.relative_directory().rstrip(r"\/") + cov = coverage.Coverage() + cov.start() + import a # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.xml_report(a, outfile="coverage.xml") + source_path = coverage.files.relative_directory().rstrip(r"\/") - compare("gold_x_xml", "out/xml_1", scrubs=[ + compare(".", gold_path("html/gold_x_xml"), left_extra=True, scrubs=[ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), (r' version="[-.\w]+"', ' version="VERSION"'), - (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path), + (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % re.escape(source_path)), (r'/coverage.readthedocs.io/?[-.\w/]*', '/coverage.readthedocs.io/VER'), ]) def test_a_xml_2(self): - self.output_dir("out/xml_2") - - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(config_file="run_a_xml_2.ini") - cov.start() - import a # pragma: nested - cov.stop() # pragma: nested - cov.xml_report(a) - source_path = coverage.files.relative_directory().rstrip(r"\/") - - compare("gold_x_xml", "out/xml_2", scrubs=[ + self.make_file("a.py", """\ + if 1 < 2: + # Needed a < to look at HTML entities. + a = 3 + else: + a = 4 + """) + + self.make_file("run_a_xml_2.ini", """\ + # Put all the XML output in xml_2 + [xml] + output = xml_2/coverage.xml + """) + + cov = coverage.Coverage(config_file="run_a_xml_2.ini") + cov.start() + import a # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.xml_report(a) + source_path = coverage.files.relative_directory().rstrip(r"\/") + + compare("xml_2", gold_path("html/gold_x_xml"), scrubs=[ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), (r' version="[-.\w]+"', ' version="VERSION"'), - (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path), + (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % re.escape(source_path)), (r'/coverage.readthedocs.io/?[-.\w/]*', '/coverage.readthedocs.io/VER'), ]) def test_y_xml_branch(self): - self.output_dir("out/y_xml_branch") - - with change_dir("src"): - # pylint: disable=import-error - cov = coverage.Coverage(branch=True) - cov.start() - import y # pragma: nested - cov.stop() # pragma: nested - cov.xml_report(y, outfile="../out/y_xml_branch/coverage.xml") - source_path = coverage.files.relative_directory().rstrip(r"\/") - - compare("gold_y_xml_branch", "out/y_xml_branch", scrubs=[ + self.make_file("y.py", """\ + def choice(x): + if x < 2: + return 3 + else: + return 4 + + assert choice(1) == 3 + """) + + cov = coverage.Coverage(branch=True) + cov.start() + import y # pragma: nested # pylint: disable=import-error + cov.stop() # pragma: nested + cov.xml_report(y, outfile="y_xml_branch/coverage.xml") + source_path = coverage.files.relative_directory().rstrip(r"\/") + + compare("y_xml_branch", gold_path("html/gold_y_xml_branch"), scrubs=[ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), (r' version="[-.\w]+"', ' version="VERSION"'), - (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path), + (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % re.escape(source_path)), (r'/coverage.readthedocs.io/?[-.\w/]*', '/coverage.readthedocs.io/VER'), ]) diff --git a/tox-new.ini b/tox-new.ini deleted file mode 100644 index bc5f041a..00000000 --- a/tox-new.ini +++ /dev/null @@ -1,53 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -# An experiment in using tox to install the sdist, and do the pytracer/ctracer -# split. Doesn't yet work because the working tree is in the import path, so -# "import coverage" finds the working tree instead of the sdist-installed code. -# This can be fixed one of two ways: -# -# 1. By changing to a "src" layout, so that "import coverage" won't work in the -# working tree, or -# -# 2. By removing the "__init__.py" from the tests directory, so that nose won't -# add the working tree to the path. This will also mean changing a number of -# import statements in the tests directory. - -[tox] -envlist = py{26,27,33,34,35}-{c,py}tracer, pypy{24,26,3_24}-pytracer -skip_missing_interpreters = True - -[testenv] -commands = - # Create tests/zipmods.zip, install the egg1 egg - python igor.py zip_mods install_egg - - # Remove the C extension so that we can test the PyTracer - pytracer: python igor.py remove_extension - pytracer: python igor.py test_with_tracer py {posargs} - - ctracer: python igor.py test_with_tracer c {posargs} - -deps = - # https://requires.io/github/nedbat/coveragepy/requirements/ - nose==1.3.7 - mock==1.3.0 - PyContracts==1.7.6 - py26: unittest2==1.1.0 - py{26,27}: gevent==1.0.2 - py{26,27}: eventlet==0.17.4 - py{26,27,33,34,35}: greenlet==0.4.9 - -passenv = COVERAGE_* - -[testenv:pypy] -basepython = pypy - -[testenv:pypy24] -basepython = pypy2.4 - -[testenv:pypy26] -basepython = pypy2.6 - -[testenv:pypy3_24] -basepython = pypy3-2.4 @@ -1,8 +1,8 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt [tox] -envlist = py{26,27,33,34,35,36,37}, pypy{2,3}, jython, doc, lint +envlist = py{27,34,35,36,37}, pypy{2,3}, doc, lint skip_missing_interpreters = {env:COVERAGE_SKIP_MISSING_INTERPRETERS:True} toxworkdir = {env:TOXWORKDIR:.tox} @@ -10,20 +10,15 @@ toxworkdir = {env:TOXWORKDIR:.tox} usedevelop = True deps = - # https://requires.io/github/nedbat/coveragepy/requirements/ + # Check here for what might be out of date: + # https://requires.io/github/nedbat/coveragepy/requirements/ -rrequirements/pytest.pip - pip==9.0.1 - # setuptools>=36 vendors packages which pollute the coverage output in tests - setuptools==35.0.2 - mock==2.0.0 - PyContracts==1.8.0 - unittest-mixins==1.4 - #-e/Users/ned/unittest_mixins - py26: unittest2==1.1.0 - py{27,33,34,35,36}: gevent==1.2.2 - py26: eventlet==0.21.0 - py{27,33,34,35,36,37}: eventlet==0.22.0 - py{26,27,33,34,35,36,37}: greenlet==0.4.13 + pip==18.0 + setuptools==40.0.0 + # gevent 1.3 causes a failure: https://bitbucket.org/ned/coveragepy/issues/663/gevent-132-on-windows-fails + py{27,34,35,36}: gevent==1.2.2 + py{27,34,35,36,37}: eventlet==0.24.1 + py{27,34,35,36,37}: greenlet==0.4.14 # Windows can't update the pip version with pip running, so use Python # to install things. @@ -43,13 +38,6 @@ commands = # Remove the C extension so that we can test the PyTracer python igor.py zip_mods install_egg remove_extension - # When running parallel tests, many processes might all try to import the - # same modules at once. This should be safe, but especially on Python 3.3, - # this caused a number of test failures trying to import usepkgs. To - # prevent the race condition, pre-compile the tests/modules directory. - py33: python -m compileall -q -f tests/modules - py33: python -c "import time; time.sleep(1.1)" - # Test with the PyTracer python igor.py test_with_tracer py {posargs} @@ -57,9 +45,6 @@ commands = python setup.py --quiet build_ext --inplace python igor.py test_with_tracer c {posargs} -[testenv:py26] -install_command = python -m pip.__main__ install -U {opts} {packages} - [testenv:pypy] # The "pypy" environment is for Travis. Probably can make Travis use one of # the other environments... @@ -93,6 +78,18 @@ setenv = LINTABLE = coverage tests igor.py setup.py __main__.py commands = - python -m pylint --notes= {env:LINTABLE} python -m tabnanny {env:LINTABLE} python igor.py check_eol + check-manifest --ignore 'lab*,perf*,doc/sample_html*,.treerc' + python setup.py check -r -s + python -m pylint --notes= {env:LINTABLE} + +[travis] +#2.7: py27, lint +python = + 2.7: py27 + 3.4: py34 + 3.5: py35 + 3.6: py36 + pypy: pypy + pypy3.5: pypy3 diff --git a/tox_wheels.ini b/tox_wheels.ini index 12f4a129..7381cafb 100644 --- a/tox_wheels.ini +++ b/tox_wheels.ini @@ -1,15 +1,15 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt [tox] -envlist = py{26,27,33,34,35,36,37,sys} +envlist = py{27,34,35,36,37,sys} toxworkdir = {toxinidir}/.tox_kits [testenv] deps = -rrequirements/wheel.pip -commands = +commands = python -c "import sys; print(sys.real_prefix)" python setup.py bdist_wheel {posargs} |