diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml
index a0da0a1ea..97b4d88d3 100644
--- a/.github/workflows/cancel.yml
+++ b/.github/workflows/cancel.yml
@@ -4,7 +4,7 @@
# This action finds in-progress Action jobs for the same branch, and cancels
# them. There's little point in continuing to run superceded jobs.
-name: Cancel
+name: "Cancel"
on:
push:
@@ -13,7 +13,7 @@ jobs:
cancel:
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
+ - name: "Cancel Previous Runs"
uses: styfle/cancel-workflow-action@0.6.0
with:
access_token: ${{ github.token }}
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index ad5a21cf8..ee798ada1 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -4,9 +4,11 @@
name: "Coverage"
on:
+ # As currently structured, this adds too many jobs (checks?), so don't run it
+ # on pull requests yet.
push:
- branches: ["master"]
- pull_request:
+ branches:
+ - master
workflow_dispatch:
defaults:
@@ -15,32 +17,53 @@ defaults:
jobs:
coverage:
- name: "Python ${{ matrix.python-version }}"
- runs-on: ubuntu-latest
+ name: "Python ${{ matrix.python-version }} on ${{ matrix.os }}"
+ runs-on: "${{ matrix.os }}"
strategy:
matrix:
+ os:
+ - ubuntu-latest
+ - macos-latest
+ - windows-latest
python-version:
+ # When changing this list, be sure to check the [gh-actions] list in
+ # tox.ini so that tox will run properly.
- "2.7"
- "3.5"
- "3.9"
+ - "3.10.0-alpha.5"
- "pypy3"
- fail-fast: false
+ exclude:
+ # Windows PyPy doesn't seem to work?
+ - os: windows-latest
+ python-version: "pypy3"
+ # If one job fails, stop the whole thing.
+ fail-fast: true
steps:
- name: "Check out the repo"
uses: "actions/checkout@v2"
+ with:
+ fetch-depth: "0"
- name: "Set up Python"
uses: "actions/setup-python@v2"
with:
python-version: "${{ matrix.python-version }}"
+ - name: "Install Visual C++ if needed"
+ if: runner.os == 'Windows' && matrix.python-version == '2.7'
+ run: |
+ choco install vcpython27 -f -y
+
- name: "Install dependencies"
run: |
set -xe
python -VV
python -m site
+ # Need to install setuptools first so that ci.pip will succeed.
+ python -m pip install -c requirements/pins.pip setuptools wheel
python -m pip install -r requirements/ci.pip
python -m pip install -c requirements/pins.pip tox-gh-actions
@@ -50,14 +73,22 @@ jobs:
run: |
set -xe
python -m tox
- python -m igor combine_html
- mv .metacov .coverage.${{ matrix.python-version }}
+
+ - name: "Combine"
+ env:
+ COVERAGE_COVERAGE: "yes"
+ COVERAGE_RCFILE: "metacov.ini"
+ COVERAGE_METAFILE: ".metacov"
+ run: |
+ set -xe
+ COVERAGE_DEBUG=dataio python -m igor combine_html
+ mv .metacov .metacov.${{ matrix.python-version }}.${{ matrix.os }}
- name: "Upload coverage data"
uses: actions/upload-artifact@v2
with:
name: metacov
- path: .coverage.*
+ path: .metacov.*
combine:
name: "Combine coverage data"
@@ -67,6 +98,8 @@ jobs:
steps:
- name: "Check out the repo"
uses: "actions/checkout@v2"
+ with:
+ fetch-depth: "0"
- name: "Set up Python"
uses: "actions/setup-python@v2"
@@ -78,7 +111,6 @@ jobs:
set -xe
python -VV
python -m site
- python -m pip install -r requirements/ci.pip
python setup.py --quiet clean develop
python igor.py zip_mods install_egg
@@ -88,12 +120,77 @@ jobs:
name: metacov
- name: "Combine and report"
+ id: combine
+ env:
+ COVERAGE_RCFILE: "metacov.ini"
+ COVERAGE_METAFILE: ".metacov"
run: |
set -xe
- coverage combine
- coverage xml
+ python -m igor combine_html
+ python -m coverage json
+ echo "::set-output name=total::$(python -c "import json;print(format(json.load(open('coverage.json'))['totals']['percent_covered'],'.2f'))")"
- name: "Upload to codecov"
uses: codecov/codecov-action@v1
with:
file: coverage.xml
+
+ - name: "Upload HTML report"
+ uses: actions/upload-artifact@v2
+ with:
+ name: html_report
+ path: htmlcov
+
+ - name: "Upload JSON report"
+ uses: actions/upload-artifact@v2
+ with:
+ name: json_report
+ path: coverage.json
+
+ - name: "Create info for pushing to report repo"
+ id: info
+ run: |
+ export SHA10=$(echo ${{ github.sha }} | cut -c 1-10)
+ export SLUG=$(date +'%Y%m%d')_$SHA10
+ export REF="${{ github.ref }}"
+ echo "::set-output name=sha10::$SHA10"
+ echo "::set-output name=slug::$SLUG"
+ echo "::set-output name=url::https://nedbat.github.io/coverage-reports/reports/$SLUG/htmlcov"
+ echo "::set-output name=branch::${REF#refs/heads/}"
+
+ - name: "Push to report repository"
+ uses: sebastian-palma/github-action-push-to-another-repository@allow-creating-destination-directory
+ env:
+ API_TOKEN_GITHUB: ${{ secrets.COVERAGE_REPORTS_TOKEN }}
+ with:
+ source-directory: 'htmlcov'
+ destination-github-username: 'nedbat'
+ destination-repository-name: 'coverage-reports'
+ destination-repository-directory: 'reports/${{ steps.info.outputs.slug }}'
+ empty-repository: false
+ create-destination-directory: true
+ target-branch: main
+ commit-message: >-
+ ${{ steps.combine.outputs.total }}% - ${{ github.event.head_commit.message }}
+
+
+ ${{ steps.info.outputs.url }}
+
+ ${{ steps.info.outputs.sha10 }}: ${{ steps.info.outputs.branch }}
+ user-email: ned@nedbatchelder.com
+
+ - name: "Create redirection HTML file"
+ run: |
+ echo "
" > coverage-report-redirect.html
+ echo "" >> coverage-report-redirect.html
+ echo "Coverage report redirect..." >> coverage-report-redirect.html
+
+ - name: "Upload HTML redirect"
+ uses: actions/upload-artifact@v2
+ with:
+ name: coverage-report-redirect.html
+ path: coverage-report-redirect.html
+
+ - name: "Show link to report"
+ run: |
+ echo "Coverage report: ${{ steps.info.outputs.url }}"
diff --git a/.github/workflows/kit.yml b/.github/workflows/kit.yml
index 437e7d31b..854b4f299 100644
--- a/.github/workflows/kit.yml
+++ b/.github/workflows/kit.yml
@@ -4,7 +4,7 @@
# Based on:
# https://github.com/joerick/cibuildwheel/blob/master/examples/github-deploy.yml
-name: Build kits
+name: "Kits"
on:
workflow_dispatch:
@@ -15,88 +15,91 @@ defaults:
jobs:
build_wheels:
- name: Build wheels on ${{ matrix.os }}
+ name: "Build wheels on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest, windows-latest, macos-latest]
+ os:
+ - ubuntu-latest
+ - windows-latest
+ - macos-latest
fail-fast: false
steps:
- - name: Check out the repo
+ - name: "Check out the repo"
uses: actions/checkout@v2
- - name: Install Python 3.7
+ - name: "Install Python 3.7"
uses: actions/setup-python@v2
with:
python-version: "3.7"
- - name: Install cibuildwheel
+ - name: "Install cibuildwheel"
run: |
python -m pip install -c requirements/pins.pip cibuildwheel
- - name: Install Visual C++ for Python 2.7
+ - name: "Install Visual C++ for Python 2.7"
if: runner.os == 'Windows'
run: |
choco install vcpython27 -f -y
- - name: Build wheels
+ - name: "Build wheels"
env:
# Don't build wheels for PyPy.
CIBW_SKIP: pp*
run: |
python -m cibuildwheel --output-dir wheelhouse
- - name: Upload wheels
+ - name: "Upload wheels"
uses: actions/upload-artifact@v2
with:
name: dist
path: ./wheelhouse/*.whl
build_sdist:
- name: Build source distribution
+ name: "Build source distribution"
runs-on: ubuntu-latest
steps:
- - name: Check out the repo
+ - name: "Check out the repo"
uses: actions/checkout@v2
- - name: Install Python 3.7
+ - name: "Install Python 3.7"
uses: actions/setup-python@v2
with:
python-version: "3.7"
- - name: Build sdist
+ - name: "Build sdist"
run: |
python setup.py sdist
- - name: Upload sdist
+ - name: "Upload sdist"
uses: actions/upload-artifact@v2
with:
name: dist
path: dist/*.tar.gz
build_pypy:
- name: Build PyPy wheels
+ name: "Build PyPy wheels"
runs-on: ubuntu-latest
steps:
- - name: Check out the repo
+ - name: "Check out the repo"
uses: actions/checkout@v2
- - name: Install PyPy
+ - name: "Install PyPy"
uses: actions/setup-python@v2
with:
python-version: "pypy3"
- - name: Install requirements
+ - name: "Install requirements"
run: |
pypy3 -m pip install -r requirements/wheel.pip
- - name: Build wheels
+ - name: "Build wheels"
run: |
pypy3 setup.py bdist_wheel --python-tag pp36
pypy3 setup.py bdist_wheel --python-tag pp37
- - name: Upload wheels
+ - name: "Upload wheels"
uses: actions/upload-artifact@v2
with:
name: dist
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index fbd3d8328..1a1b7f03f 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -1,11 +1,12 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
-name: "Quality checks"
+name: "Quality"
on:
push:
- branches: ["master"]
+ branches:
+ - master
pull_request:
workflow_dispatch:
@@ -15,7 +16,7 @@ defaults:
jobs:
lint:
- name: Pylint etc
+ name: "Pylint etc"
# Because pylint can report different things on different OS's (!)
# (https://github.com/PyCQA/pylint/issues/3489), run this on Mac where local
# pylint gets run.
@@ -42,7 +43,7 @@ jobs:
python -m tox -e lint
doc:
- name: Build docs
+ name: "Build docs"
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml
index 59f5380b2..a88bfba4c 100644
--- a/.github/workflows/testsuite.yml
+++ b/.github/workflows/testsuite.yml
@@ -1,11 +1,12 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
-name: "Test Suite"
+name: "Tests"
on:
push:
- branches: ["master"]
+ branches:
+ - master
pull_request:
workflow_dispatch:
@@ -20,14 +21,20 @@ jobs:
strategy:
matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
+ os:
+ - ubuntu-latest
+ - macos-latest
+ - windows-latest
python-version:
+ # When changing this list, be sure to check the [gh-actions] list in
+ # tox.ini so that tox will run properly.
- "2.7"
- "3.5"
- "3.6"
- "3.7"
- "3.8"
- "3.9"
+ - "3.10.0-alpha.5"
- "pypy3"
exclude:
# Windows PyPy doesn't seem to work?
@@ -54,6 +61,8 @@ jobs:
set -xe
python -VV
python -m site
+ # Need to install setuptools first so that ci.pip will succeed.
+ python -m pip install -c requirements/pins.pip setuptools wheel
python -m pip install -r requirements/ci.pip
python -m pip install -c requirements/pins.pip tox-gh-actions
diff --git a/CHANGES.rst b/CHANGES.rst
index 98f632842..afd5f16ae 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -21,6 +21,42 @@ want to know what's different in 5.0 since 4.5.x, see :ref:`whatsnew5x`.
.. Version 9.8.1 --- 2027-07-27
.. ----------------------------
+.. _changes_55:
+
+Version 5.5 --- 2021-02-28
+--------------------------
+
+- ``coverage combine`` has a new option, ``--keep`` to keep the original data
+ files after combining them. The default is still to delete the files after
+ they have been combined. This was requested in `issue 1108`_ and implemented
+ in `pull request 1110`_. Thanks, Éric Larivière.
+
+- When reporting missing branches in ``coverage report``, branches aren't
+ reported that jump to missing lines. This adds to the long-standing behavior
+ of not reporting branches from missing lines. Now branches are only reported
+ if both the source and destination lines are executed. Closes both `issue
+ 1065`_ and `issue 955`_.
+
+- Minor improvements to the HTML report:
+
+ - The state of the line visibility selector buttons is saved in local storage
+ so you don't have to fiddle with them so often, fixing `issue 1123`_.
+
+ - It has a little more room for line numbers so that 4-digit numbers work
+ well, fixing `issue 1124`_.
+
+- Improved the error message when combining line and branch data, so that users
+ will be more likely to understand what's happening, closing `issue 803`_.
+
+.. _issue 803: https://github.com/nedbat/coveragepy/issues/803
+.. _issue 955: https://github.com/nedbat/coveragepy/issues/955
+.. _issue 1065: https://github.com/nedbat/coveragepy/issues/1065
+.. _issue 1108: https://github.com/nedbat/coveragepy/issues/1108
+.. _pull request 1110: https://github.com/nedbat/coveragepy/pull/1110
+.. _issue 1123: https://github.com/nedbat/coveragepy/issues/1123
+.. _issue 1124: https://github.com/nedbat/coveragepy/issues/1124
+
+
.. _changes_54:
Version 5.4 --- 2021-01-24
@@ -37,8 +73,8 @@ Version 5.4 --- 2021-01-24
``[report]`` settings if there isn't a value in the ``[html]`` section.
Closes `issue 1090`_.
-- Combining files on Windows across drives how works properly, fixing `issue
- 577`_. Thanks, `Valentine Lab `_.
+- Combining files on Windows across drives now works properly, fixing `issue
+ 577`_. Thanks, `Valentin Lab `_.
- Fix an obscure warning from deep in the _decimal module, as reported in
`issue 1084`_.
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 455c40967..76fbd4c31 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -19,6 +19,7 @@ Anthony Sottile
Arcadiy Ivanov
Aron Griffis
Artem Dayneko
+Arthur Deygin
Ben Finney
Bernát Gábor
Bill Hart
@@ -54,9 +55,10 @@ Dirk Thomas
Dmitry Shishov
Dmitry Trofimov
Eduardo Schettino
+Edward Loper
Eli Skeggs
Emil Madsen
-Edward Loper
+Éric Larivière
Federico Bond
Frazer McLean
Geoff Bache
@@ -136,7 +138,7 @@ Ted Wexler
Thijs Triemstra
Thomas Grainger
Titus Brown
-Valentine Lab
+Valentin Lab
Vince Salvino
Ville Skyttä
Xie Yanbo
diff --git a/Makefile b/Makefile
index ff5d3c999..d7bc15b7d 100644
--- a/Makefile
+++ b/Makefile
@@ -96,9 +96,12 @@ kit_local:
# don't go crazy trying to figure out why our new code isn't installing.
find ~/Library/Caches/pip/wheels -name 'coverage-*' -delete
-download_kits: ## Download the built kits from GitHub
+download_kits: ## Download the built kits from GitHub.
python ci/download_gha_artifacts.py
+check_kits: ## Check that dist/* are well-formed.
+ python -m twine check dist/*
+
build_ext:
python setup.py build_ext
@@ -118,6 +121,8 @@ $(DOCBIN):
cmd_help: $(DOCBIN)
@for cmd in annotate combine debug erase html json report run xml; do \
echo > doc/help/$$cmd.rst; \
+ echo ".. This file is auto-generated by \"make dochtml\", don't edit it manually." >> doc/help/$$cmd.rst; \
+ echo >> doc/help/$$cmd.rst; \
echo ".. code::" >> doc/help/$$cmd.rst; \
echo >> doc/help/$$cmd.rst; \
echo " $$ coverage $$cmd --help" >> doc/help/$$cmd.rst; \
diff --git a/README.rst b/README.rst
index 66cd938a8..072f30ffe 100644
--- a/README.rst
+++ b/README.rst
@@ -8,7 +8,7 @@ Coverage.py
Code coverage testing for Python.
| |license| |versions| |status|
-| |ci-status| |docs| |codecov|
+| |test-status| |quality-status| |docs| |codecov|
| |kit| |format| |repos| |downloads|
| |stars| |forks| |contributors|
| |tidelift| |twitter-coveragepy| |twitter-nedbat|
@@ -95,9 +95,12 @@ Licensed under the `Apache 2.0 License`_. For details, see `NOTICE.txt`_.
.. _NOTICE.txt: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
-.. |ci-status| image:: https://github.com/nedbat/coveragepy/workflows/Test%20Suite/badge.svg
- :target: https://github.com/nedbat/coveragepy/actions?query=workflow%3A%22Test+Suite%22
- :alt: Build status
+.. |test-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml/badge.svg?branch=master&event=push
+ :target: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml
+ :alt: Test suite status
+.. |quality-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml/badge.svg?branch=master&event=push
+ :target: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml
+ :alt: Quality check status
.. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat
:target: https://coverage.readthedocs.io/
:alt: Documentation
diff --git a/coverage/backunittest.py b/coverage/backunittest.py
deleted file mode 100644
index 123bb2a13..000000000
--- a/coverage/backunittest.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
-# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
-
-"""Implementations of unittest features from the future."""
-
-import unittest
-
-
-def unittest_has(method):
- """Does `unittest.TestCase` have `method` defined?"""
- return hasattr(unittest.TestCase, method)
-
-
-class TestCase(unittest.TestCase):
- """Just like unittest.TestCase, but with assert methods added.
-
- Designed to be compatible with 3.1 unittest. Methods are only defined if
- `unittest` doesn't have them.
-
- """
- # pylint: disable=signature-differs, deprecated-method
-
- if not unittest_has('assertCountEqual'):
- def assertCountEqual(self, *args, **kwargs):
- return self.assertItemsEqual(*args, **kwargs)
-
- if not unittest_has('assertRaisesRegex'):
- def assertRaisesRegex(self, *args, **kwargs):
- return self.assertRaisesRegexp(*args, **kwargs)
-
- if not unittest_has('assertRegex'):
- def assertRegex(self, *args, **kwargs):
- return self.assertRegexpMatches(*args, **kwargs)
diff --git a/coverage/backward.py b/coverage/backward.py
index 8af3452b2..ac781ab96 100644
--- a/coverage/backward.py
+++ b/coverage/backward.py
@@ -78,7 +78,9 @@
try:
import reprlib
-except ImportError:
+except ImportError: # pragma: not covered
+ # We need this on Python 2, but in testing environments, a backport is
+ # installed, so this import isn't used.
import repr as reprlib
# A function to iterate listlessly over a dict's items, and one to get the
@@ -215,9 +217,6 @@ def __repr__(self):
items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys)
return "{}({})".format(type(self).__name__, ", ".join(items))
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
def format_local_datetime(dt):
"""Return a string with local timezone representing the date.
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 9c9ae868a..0be0cca19 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -31,6 +31,10 @@ class Opts(object):
'-a', '--append', action='store_true',
help="Append coverage data to .coverage, otherwise it starts clean each time.",
)
+ keep = optparse.make_option(
+ '', '--keep', action='store_true',
+ help="Keep original coverage files, otherwise they are deleted.",
+ )
branch = optparse.make_option(
'', '--branch', action='store_true',
help="Measure branch coverage in addition to statement coverage.",
@@ -215,6 +219,7 @@ def __init__(self, *args, **kwargs):
help=None,
ignore_errors=None,
include=None,
+ keep=None,
module=None,
omit=None,
contexts=None,
@@ -333,6 +338,7 @@ def get_prog_name(self):
"combine",
[
Opts.append,
+ Opts.keep,
] + GLOBAL_ARGS,
usage="[options] ... ",
description=(
@@ -585,7 +591,7 @@ def command_line(self, argv):
if options.append:
self.coverage.load()
data_dirs = args or None
- self.coverage.combine(data_dirs, strict=True)
+ self.coverage.combine(data_dirs, strict=True, keep=bool(options.keep))
self.coverage.save()
return OK
@@ -765,7 +771,7 @@ def do_debug(self, args):
self.coverage.load()
data = self.coverage.get_data()
print(info_header("data"))
- print("path: %s" % self.coverage.get_data().data_filename())
+ print("path: %s" % data.data_filename())
if data:
print("has_arcs: %r" % data.has_arcs())
summary = line_counts(data, fullpath=True)
diff --git a/coverage/config.py b/coverage/config.py
index 803dcd5d7..7ef7e7ae7 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -63,7 +63,7 @@ def options(self, section):
real_section = section_prefix + section
if configparser.RawConfigParser.has_section(self, real_section):
return configparser.RawConfigParser.options(self, real_section)
- raise configparser.NoSectionError
+ raise configparser.NoSectionError(section)
def get_section(self, section):
"""Get the contents of a section, as a dictionary."""
@@ -87,7 +87,7 @@ def get(self, section, option, *args, **kwargs):
if configparser.RawConfigParser.has_option(self, real_section, option):
break
else:
- raise configparser.NoOptionError
+ raise configparser.NoOptionError(option, section)
v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs)
v = substitute_variables(v, os.environ)
@@ -477,6 +477,20 @@ def get_option(self, option_name):
# If we get here, we didn't find the option.
raise CoverageException("No such option: %r" % option_name)
+ def post_process_file(self, path):
+ """Make final adjustments to a file path to make it usable."""
+ return os.path.expanduser(path)
+
+ def post_process(self):
+ """Make final adjustments to settings to make them usable."""
+ self.data_file = self.post_process_file(self.data_file)
+ self.html_dir = self.post_process_file(self.html_dir)
+ self.xml_output = self.post_process_file(self.xml_output)
+ self.paths = collections.OrderedDict(
+ (k, [self.post_process_file(f) for f in v])
+ for k, v in self.paths.items()
+ )
+
def config_files_to_try(config_file):
"""What config files should we try to read?
@@ -551,12 +565,6 @@ def read_coverage_config(config_file, **kwargs):
# Once all the config has been collected, there's a little post-processing
# to do.
- config.data_file = os.path.expanduser(config.data_file)
- config.html_dir = os.path.expanduser(config.html_dir)
- config.xml_output = os.path.expanduser(config.xml_output)
- config.paths = collections.OrderedDict(
- (k, [os.path.expanduser(f) for f in v])
- for k, v in config.paths.items()
- )
+ config.post_process()
return config
diff --git a/coverage/control.py b/coverage/control.py
index 8d129bcb5..1623b0932 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -659,7 +659,7 @@ def save(self):
data = self.get_data()
data.write()
- def combine(self, data_paths=None, strict=False):
+ def combine(self, data_paths=None, strict=False, keep=False):
"""Combine together a number of similarly-named coverage data files.
All coverage data files whose name starts with `data_file` (from the
@@ -674,12 +674,16 @@ def combine(self, data_paths=None, strict=False):
If `strict` is true, then it is an error to attempt to combine when
there are no data files to combine.
+ If `keep` is true, then original input data files won't be deleted.
+
.. versionadded:: 4.0
The `data_paths` parameter.
.. versionadded:: 4.3
The `strict` parameter.
+ .. versionadded: 5.5
+ The `keep` parameter.
"""
self._init()
self._init_data(suffix=None)
@@ -694,7 +698,13 @@ def combine(self, data_paths=None, strict=False):
for pattern in paths[1:]:
aliases.add(pattern, result)
- 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,
+ keep=keep,
+ )
def get_data(self):
"""Get the collected data.
diff --git a/coverage/data.py b/coverage/data.py
index 82bf1d41c..5dd1dfe3f 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -52,7 +52,7 @@ def add_data_to_hash(data, filename, hasher):
hasher.update(data.file_tracer(filename))
-def combine_parallel_data(data, aliases=None, data_paths=None, strict=False):
+def combine_parallel_data(data, aliases=None, data_paths=None, strict=False, keep=False):
"""Combine a number of data files together.
Treat `data.filename` as a file prefix, and combine the data from all
@@ -68,7 +68,7 @@ def combine_parallel_data(data, aliases=None, data_paths=None, strict=False):
If `data_paths` is not provided, then the directory portion of
`data.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
+ Unless `keep` is True 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.
@@ -116,9 +116,10 @@ def combine_parallel_data(data, aliases=None, data_paths=None, strict=False):
else:
data.update(new_data, aliases=aliases)
files_combined += 1
- if data._debug.should('dataio'):
- data._debug.write("Deleting combined data file %r" % (f,))
- file_be_gone(f)
+ if not keep:
+ if 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")
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
index 6bc9fdf59..27b49b36f 100644
--- a/coverage/htmlfiles/coverage_html.js
+++ b/coverage/htmlfiles/coverage_html.js
@@ -233,6 +233,8 @@ coverage.index_ready = function ($) {
// -- pyfile stuff --
+coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
+
coverage.pyfile_ready = function ($) {
// If we're directed to a particular line number, highlight the line.
var frag = location.hash;
@@ -256,6 +258,22 @@ coverage.pyfile_ready = function ($) {
$(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");});
$(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");});
+ coverage.filters = undefined;
+ try {
+ coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
+ } catch(err) {}
+
+ if (coverage.filters) {
+ coverage.filters = JSON.parse(coverage.filters);
+ }
+ else {
+ coverage.filters = {run: false, exc: true, mis: true, par: true};
+ }
+
+ for (cls in coverage.filters) {
+ coverage.set_line_visibilty(cls, coverage.filters[cls]);
+ }
+
coverage.assign_shortkeys();
coverage.wire_up_help_panel();
@@ -266,17 +284,26 @@ coverage.pyfile_ready = function ($) {
};
coverage.toggle_lines = function (btn, cls) {
- btn = $(btn);
- var show = "show_"+cls;
- if (btn.hasClass(show)) {
- $("#source ." + cls).removeClass(show);
- btn.removeClass(show);
- }
- else {
+ var onoff = !$(btn).hasClass("show_" + cls);
+ coverage.set_line_visibilty(cls, onoff);
+ coverage.build_scroll_markers();
+ coverage.filters[cls] = onoff;
+ try {
+ localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
+ } catch(err) {}
+};
+
+coverage.set_line_visibilty = function (cls, onoff) {
+ var show = "show_" + cls;
+ var btn = $(".button_toggle_" + cls);
+ if (onoff) {
$("#source ." + cls).addClass(show);
btn.addClass(show);
}
- coverage.build_scroll_markers();
+ else {
+ $("#source ." + cls).removeClass(show);
+ btn.removeClass(show);
+ }
};
// Return the nth line div.
diff --git a/coverage/htmlfiles/style.css b/coverage/htmlfiles/style.css
index 3e7f9b66b..36ee2a6e6 100644
--- a/coverage/htmlfiles/style.css
+++ b/coverage/htmlfiles/style.css
@@ -34,7 +34,7 @@ a.nav:hover { text-decoration: underline; color: inherit; }
@media (prefers-color-scheme: dark) { #header { border-color: #333; } }
-.indexfile #footer { margin: 1rem 3rem; }
+.indexfile #footer { margin: 1rem 3.5rem; }
.pyfile #footer { margin: 1rem 1rem; }
@@ -42,9 +42,9 @@ a.nav:hover { text-decoration: underline; color: inherit; }
@media (prefers-color-scheme: dark) { #footer .content { color: #aaa; } }
-#index { margin: 1rem 0 0 3rem; }
+#index { margin: 1rem 0 0 3.5rem; }
-#header .content { padding: 1rem 3rem; }
+#header .content { padding: 1rem 3.5rem; }
h1 { font-size: 1.25em; display: inline-block; }
@@ -122,13 +122,13 @@ h2.stats { margin-top: .5em; font-size: 1em; }
.keyhelp .key { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; }
-#source { padding: 1em 0 1em 3rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
+#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
#source p { position: relative; white-space: pre; }
#source p * { box-sizing: border-box; }
-#source p .n { float: left; text-align: right; width: 3rem; box-sizing: border-box; margin-left: -3rem; padding-right: 1em; color: #999; }
+#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; }
@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }
diff --git a/coverage/htmlfiles/style.scss b/coverage/htmlfiles/style.scss
index 8169269e3..158d1fb49 100644
--- a/coverage/htmlfiles/style.scss
+++ b/coverage/htmlfiles/style.scss
@@ -16,7 +16,7 @@
/* Don't edit this .css file. Edit the .scss file instead! */
// Dimensions
-$left-gutter: 3rem;
+$left-gutter: 3.5rem;
//
@@ -166,7 +166,7 @@ a.nav {
}
.indexfile #footer {
- margin: 1rem 3rem;
+ margin: 1rem $left-gutter;
}
.pyfile #footer {
@@ -181,7 +181,7 @@ a.nav {
}
#index {
- margin: 1rem 0 0 3rem;
+ margin: 1rem 0 0 $left-gutter;
}
// Header styles
diff --git a/coverage/misc.py b/coverage/misc.py
index 96573f7a4..034e288eb 100644
--- a/coverage/misc.py
+++ b/coverage/misc.py
@@ -63,7 +63,7 @@ def _decorator(func):
def new_contract(*args, **kwargs):
"""A proxy for contracts.new_contract that doesn't mind happening twice."""
try:
- return raw_new_contract(*args, **kwargs)
+ raw_new_contract(*args, **kwargs)
except ValueError:
# During meta-coverage, this module is imported twice, and
# PyContracts doesn't like redefining contracts. It's OK.
diff --git a/coverage/multiproc.py b/coverage/multiproc.py
index 0afcb0c9b..8b6651bc5 100644
--- a/coverage/multiproc.py
+++ b/coverage/multiproc.py
@@ -28,7 +28,7 @@
class ProcessWithCoverage(OriginalProcess): # pylint: disable=abstract-method
"""A replacement for multiprocess.Process that starts coverage."""
- def _bootstrap(self, *args, **kwargs): # pylint: disable=signature-differs
+ def _bootstrap(self, *args, **kwargs):
"""Wrapper around _bootstrap to start coverage."""
try:
from coverage import Coverage # avoid circular import
diff --git a/coverage/pytracer.py b/coverage/pytracer.py
index 44bfc8d6a..7ab4d3ef9 100644
--- a/coverage/pytracer.py
+++ b/coverage/pytracer.py
@@ -14,6 +14,11 @@
if env.PY2:
YIELD_VALUE = chr(YIELD_VALUE)
+# When running meta-coverage, this file can try to trace itself, which confuses
+# everything. Don't trace ourselves.
+
+THIS_FILE = __file__.rstrip("co")
+
class PyTracer(object):
"""Python implementation of the raw data tracer."""
@@ -72,25 +77,47 @@ def __repr__(self):
def log(self, marker, *args):
"""For hard-core logging of what this tracer is doing."""
with open("/tmp/debug_trace.txt", "a") as f:
- f.write("{} {:x}.{:x}[{}] {:x} {}\n".format(
+ f.write("{} {}[{}]".format(
marker,
id(self),
- self.thread.ident,
len(self.data_stack),
- self.threading.currentThread().ident,
- " ".join(map(str, args))
))
+ if 0:
+ f.write(".{:x}.{:x}".format(
+ self.thread.ident,
+ self.threading.currentThread().ident,
+ ))
+ f.write(" {}".format(" ".join(map(str, args))))
+ if 0:
+ f.write(" | ")
+ stack = " / ".join(
+ (fname or "???").rpartition("/")[-1]
+ for _, fname, _, _ in self.data_stack
+ )
+ f.write(stack)
+ f.write("\n")
def _trace(self, frame, event, arg_unused):
"""The trace function passed to sys.settrace."""
- #self.log(":", frame.f_code.co_filename, frame.f_lineno, event)
+ if THIS_FILE in frame.f_code.co_filename:
+ return None
+
+ #self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event)
if (self.stopped and sys.gettrace() == self._trace): # pylint: disable=comparison-with-callable
# The PyTrace.stop() method has been called, possibly by another
# thread, let's deactivate ourselves now.
- #self.log("X", frame.f_code.co_filename, frame.f_lineno)
+ if 0:
+ self.log("---\nX", frame.f_code.co_filename, frame.f_lineno)
+ f = frame
+ while f:
+ self.log(">", f.f_code.co_filename, f.f_lineno, f.f_code.co_name, f.f_trace)
+ f = f.f_back
sys.settrace(None)
+ self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = (
+ self.data_stack.pop()
+ )
return None
if self.last_exc_back:
@@ -104,6 +131,9 @@ def _trace(self, frame, event, arg_unused):
)
self.last_exc_back = None
+ # if event != 'call' and frame.f_code.co_filename != self.cur_file_name:
+ # self.log("---\n*", frame.f_code.co_filename, self.cur_file_name, frame.f_lineno)
+
if event == 'call':
# Should we start a new context?
if self.should_start_context and self.context is None:
@@ -153,8 +183,7 @@ def _trace(self, frame, event, arg_unused):
# Record an executed line.
if self.cur_file_dict is not None:
lineno = frame.f_lineno
- #if frame.f_code.co_filename != self.cur_file_name:
- # self.log("*", frame.f_code.co_filename, self.cur_file_name, lineno)
+
if self.trace_arcs:
self.cur_file_dict[(self.last_line, lineno)] = None
else:
diff --git a/coverage/results.py b/coverage/results.py
index ae8366bf5..4916864df 100644
--- a/coverage/results.py
+++ b/coverage/results.py
@@ -146,10 +146,7 @@ def branch_stats(self):
stats = {}
for lnum in self._branch_lines():
exits = self.exit_counts[lnum]
- try:
- missing = len(missing_arcs[lnum])
- except KeyError:
- missing = 0
+ missing = len(missing_arcs[lnum])
stats[lnum] = (exits, exits - missing)
return stats
@@ -265,7 +262,7 @@ def __radd__(self, other):
# Implementing 0+Numbers allows us to sum() a list of Numbers.
if other == 0:
return self
- return NotImplemented
+ return NotImplemented # pragma: not covered (we never call it this way)
def _line_ranges(statements, lines):
@@ -315,7 +312,7 @@ def format_lines(statements, lines, arcs=None):
line_exits = sorted(arcs)
for line, exits in line_exits:
for ex in sorted(exits):
- if line not in lines:
+ if line not in lines and ex not in lines:
dest = (ex if ex > 0 else "exit")
line_items.append((line, "%d->%s" % (line, dest)))
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index b28b83b4f..a150fdfd0 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -486,9 +486,9 @@ def _choose_lines_or_arcs(self, lines=False, arcs=False):
assert lines or arcs
assert not (lines and arcs)
if lines and self._has_arcs:
- raise CoverageException("Can't add lines to existing arc data")
+ raise CoverageException("Can't add line measurements to existing branch data")
if arcs and self._has_lines:
- raise CoverageException("Can't add arcs to existing line data")
+ raise CoverageException("Can't add branch measurements to existing line data")
if not self._has_arcs and not self._has_lines:
self._has_lines = lines
self._has_arcs = arcs
diff --git a/coverage/summary.py b/coverage/summary.py
index 0c7fa5519..65f804700 100644
--- a/coverage/summary.py
+++ b/coverage/summary.py
@@ -8,7 +8,7 @@
from coverage import env
from coverage.report import get_analysis_to_report
from coverage.results import Numbers
-from coverage.misc import NotPython, CoverageException, output_encoding
+from coverage.misc import CoverageException, output_encoding
class SummaryReporter(object):
@@ -78,29 +78,18 @@ def report(self, morfs, outfile=None):
lines = []
for (fr, analysis) in self.fr_analysis:
- try:
- nums = analysis.numbers
-
- args = (fr.relative_filename(), nums.n_statements, nums.n_missing)
- if self.branches:
- args += (nums.n_branches, nums.n_partial_branches)
- args += (nums.pc_covered_str,)
- if self.config.show_missing:
- args += (analysis.missing_formatted(branches=True),)
- text = fmt_coverage % args
- # Add numeric percent coverage so that sorting makes sense.
- args += (nums.pc_covered,)
- lines.append((text, args))
- except Exception:
- report_it = not self.config.ignore_errors
- if report_it:
- typ, msg = sys.exc_info()[:2]
- # NotPython is only raised by PythonFileReporter, which has a
- # should_be_python() method.
- if typ is NotPython and not fr.should_be_python():
- report_it = False
- if report_it:
- self.writeout(self.fmt_err % (fr.relative_filename(), typ.__name__, msg))
+ nums = analysis.numbers
+
+ args = (fr.relative_filename(), nums.n_statements, nums.n_missing)
+ if self.branches:
+ args += (nums.n_branches, nums.n_partial_branches)
+ args += (nums.pc_covered_str,)
+ if self.config.show_missing:
+ args += (analysis.missing_formatted(branches=True),)
+ text = fmt_coverage % args
+ # Add numeric percent coverage so that sorting makes sense.
+ args += (nums.pc_covered,)
+ lines.append((text, args))
# Sort the lines and write them out.
if getattr(self.config, 'sort', None):
diff --git a/coverage/version.py b/coverage/version.py
index 8cc58dfb1..d141a11da 100644
--- a/coverage/version.py
+++ b/coverage/version.py
@@ -5,7 +5,7 @@
# This file is exec'ed in setup.py, don't import anything!
# Same semantics as sys.version_info.
-version_info = (5, 4, 0, "final", 0)
+version_info = (5, 5, 0, "final", 0)
def _make_version(major, minor, micro, releaselevel, serial):
diff --git a/doc/cmd.rst b/doc/cmd.rst
index f6087fecf..2b2086b16 100644
--- a/doc/cmd.rst
+++ b/doc/cmd.rst
@@ -287,6 +287,9 @@ setting to store relative file paths (see :ref:`relative_files
If any of the data files can't be read, coverage.py will print a warning
indicating the file and the problem.
+The original input data files are deleted once they've been combined. If you
+want to keep those files, use the ``--keep`` command-line option.
+
.. include:: help/combine.rst
diff --git a/doc/conf.py b/doc/conf.py
index b76c0a235..7ea5a8767 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -39,7 +39,8 @@
'sphinx.ext.ifconfig',
'sphinxcontrib.spelling',
'sphinx.ext.intersphinx',
- 'sphinx_rst_builder',
+ #'sphinx_rst_builder',
+ 'sphinxcontrib.restbuilder',
'sphinx.ext.extlinks',
'sphinx.ext.napoleon',
'sphinx_tabs.tabs',
@@ -66,11 +67,11 @@
# built documents.
#
# The short X.Y version.
-version = "5.4" # CHANGEME
+version = "5.5" # CHANGEME
# The full version, including alpha/beta/rc tags.
-release = "5.4" # CHANGEME
+release = "5.5" # CHANGEME
# The date of release, in "monthname day, year" format.
-release_date = "January 24, 2021" # CHANGEME
+release_date = "February 28, 2021" # CHANGEME
rst_epilog = """
.. |release_date| replace:: {release_date}
diff --git a/doc/config.rst b/doc/config.rst
index 3a8b0784d..34da8a066 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -319,6 +319,7 @@ missing lines. See :ref:`cmd_report` for more information.
``sort`` (string, default "Name"): Sort the text report by the named column.
Allowed values are "Name", "Stmts", "Miss", "Branch", "BrPart", or "Cover".
+Prefix with ``-`` for descending sort (for example, "-cover").
.. _config_html:
@@ -353,12 +354,16 @@ details.
include files in the report that are 100% covered files. See :ref:`cmd_report`
for more information.
+.. versionadded:: 5.4
+
.. _config_html_skip_empty:
``skip_empty`` (boolean, defaulted from ``[report] skip_empty``): Don't include
empty files (those that have 0 statements) in the report. See :ref:`cmd_report`
for more information.
+.. versionadded:: 5.4
+
.. _config_html_title:
``title`` (string, default "Coverage report"): the title to use for the report.
diff --git a/doc/help/annotate.rst b/doc/help/annotate.rst
index 8f0883a04..c8d597193 100644
--- a/doc/help/annotate.rst
+++ b/doc/help/annotate.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage annotate --help
diff --git a/doc/help/combine.rst b/doc/help/combine.rst
index 35180cdde..8a365958f 100644
--- a/doc/help/combine.rst
+++ b/doc/help/combine.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage combine --help
@@ -13,6 +15,7 @@
Options:
-a, --append Append coverage data to .coverage, otherwise it starts
clean each time.
+ --keep Keep original coverage files, otherwise they are deleted.
--debug=OPTS Debug options, separated by commas. [env: COVERAGE_DEBUG]
-h, --help Get help on this command.
--rcfile=RCFILE Specify configuration file. By default '.coveragerc',
diff --git a/doc/help/debug.rst b/doc/help/debug.rst
index db1e64b26..b6361da56 100644
--- a/doc/help/debug.rst
+++ b/doc/help/debug.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage debug --help
diff --git a/doc/help/erase.rst b/doc/help/erase.rst
index c8f45155a..372dd4fb6 100644
--- a/doc/help/erase.rst
+++ b/doc/help/erase.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage erase --help
diff --git a/doc/help/html.rst b/doc/help/html.rst
index 8dfa285aa..7dbf91c84 100644
--- a/doc/help/html.rst
+++ b/doc/help/html.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage html --help
diff --git a/doc/help/json.rst b/doc/help/json.rst
index cec488e55..a330167e2 100644
--- a/doc/help/json.rst
+++ b/doc/help/json.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage json --help
diff --git a/doc/help/report.rst b/doc/help/report.rst
index 3408f2bb4..b8985e4bb 100644
--- a/doc/help/report.rst
+++ b/doc/help/report.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage report --help
diff --git a/doc/help/run.rst b/doc/help/run.rst
index a336929a5..f71a09561 100644
--- a/doc/help/run.rst
+++ b/doc/help/run.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage run --help
diff --git a/doc/help/xml.rst b/doc/help/xml.rst
index eb52750d4..2ad134c91 100644
--- a/doc/help/xml.rst
+++ b/doc/help/xml.rst
@@ -1,4 +1,6 @@
+.. This file is auto-generated by "make dochtml", don't edit it manually.
+
.. code::
$ coverage xml --help
diff --git a/doc/index.rst b/doc/index.rst
index 6f408b897..63ac1d9c3 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -23,7 +23,7 @@ supported on:
.. ifconfig:: prerelease
**This is a pre-release build. The usual warnings about possible bugs
- apply.** The latest stable version is coverage.py 5.4, `described here`_.
+ apply.** The latest stable version is coverage.py 5.5, `described here`_.
.. _described here: http://coverage.readthedocs.io/
diff --git a/doc/python-coverage.1.txt b/doc/python-coverage.1.txt
index 0bbd44d0a..00c243de6 100644
--- a/doc/python-coverage.1.txt
+++ b/doc/python-coverage.1.txt
@@ -108,7 +108,8 @@ COMMAND REFERENCE
Combine data from multiple coverage files collected with ``run -p``.
The combined results are written to a single file representing the
- union of the data.
+ union of the data. Unless --keep is provided the original input
+ coverage files are deleted.
If `PATH` is specified, they are files or directories containing data to
be combined.
@@ -119,6 +120,9 @@ COMMAND REFERENCE
Append coverage data to .coverage, otherwise it starts clean each
time.
+ \--keep
+ Keep original coverage data files.
+
**debug** `TOPIC` ...
Display information about the internals of coverage.py, for diagnosing
diff --git a/doc/requirements.pip b/doc/requirements.pip
index eea4c8f99..f1f01c66d 100644
--- a/doc/requirements.pip
+++ b/doc/requirements.pip
@@ -4,9 +4,9 @@
doc8==0.8.1
pyenchant==3.2.0
-sphinx==3.3.1
-sphinx-rst-builder==0.0.3
+sphinx==3.4.3
+sphinxcontrib-restbuilder==0.3
sphinxcontrib-spelling==7.1.0
-sphinx_rtd_theme==0.5.0
+sphinx_rtd_theme==0.5.1
sphinx-autobuild==2020.9.1
-sphinx-tabs==1.3.0
+sphinx-tabs==2.0.0
diff --git a/doc/sample_html/cogapp___init___py.html b/doc/sample_html/cogapp___init___py.html
index be126eb48..7159ffdc7 100644
--- a/doc/sample_html/cogapp___init___py.html
+++ b/doc/sample_html/cogapp___init___py.html
@@ -66,8 +66,8 @@
diff --git a/doc/sample_html/cogapp___main___py.html b/doc/sample_html/cogapp___main___py.html
index 52a0236e1..56cc0db59 100644
--- a/doc/sample_html/cogapp___main___py.html
+++ b/doc/sample_html/cogapp___main___py.html
@@ -62,8 +62,8 @@
diff --git a/doc/sample_html/cogapp_backward_py.html b/doc/sample_html/cogapp_backward_py.html
index b79f30a03..0e14b51fe 100644
--- a/doc/sample_html/cogapp_backward_py.html
+++ b/doc/sample_html/cogapp_backward_py.html
@@ -99,8 +99,8 @@
diff --git a/doc/sample_html/cogapp_cogapp_py.html b/doc/sample_html/cogapp_cogapp_py.html
index 6fa600fa2..d0d330b97 100644
--- a/doc/sample_html/cogapp_cogapp_py.html
+++ b/doc/sample_html/cogapp_cogapp_py.html
@@ -865,8 +865,8 @@
diff --git a/doc/sample_html/cogapp_makefiles_py.html b/doc/sample_html/cogapp_makefiles_py.html
index 699bf9879..7d60d85f1 100644
--- a/doc/sample_html/cogapp_makefiles_py.html
+++ b/doc/sample_html/cogapp_makefiles_py.html
@@ -103,8 +103,8 @@
diff --git a/doc/sample_html/cogapp_test_cogapp_py.html b/doc/sample_html/cogapp_test_cogapp_py.html
index b7f9c9fdb..48068c7f3 100644
--- a/doc/sample_html/cogapp_test_cogapp_py.html
+++ b/doc/sample_html/cogapp_test_cogapp_py.html
@@ -2535,8 +2535,8 @@
diff --git a/doc/sample_html/cogapp_test_makefiles_py.html b/doc/sample_html/cogapp_test_makefiles_py.html
index b928d6005..1135479ca 100644
--- a/doc/sample_html/cogapp_test_makefiles_py.html
+++ b/doc/sample_html/cogapp_test_makefiles_py.html
@@ -179,8 +179,8 @@
diff --git a/doc/sample_html/cogapp_test_whiteutils_py.html b/doc/sample_html/cogapp_test_whiteutils_py.html
index 54c01da27..ed754b979 100644
--- a/doc/sample_html/cogapp_test_whiteutils_py.html
+++ b/doc/sample_html/cogapp_test_whiteutils_py.html
@@ -158,8 +158,8 @@
diff --git a/doc/sample_html/cogapp_whiteutils_py.html b/doc/sample_html/cogapp_whiteutils_py.html
index 388fa0afb..e9d33d73d 100644
--- a/doc/sample_html/cogapp_whiteutils_py.html
+++ b/doc/sample_html/cogapp_whiteutils_py.html
@@ -130,8 +130,8 @@
diff --git a/doc/sample_html/coverage_html.js b/doc/sample_html/coverage_html.js
index 6bc9fdf59..27b49b36f 100644
--- a/doc/sample_html/coverage_html.js
+++ b/doc/sample_html/coverage_html.js
@@ -233,6 +233,8 @@ coverage.index_ready = function ($) {
// -- pyfile stuff --
+coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
+
coverage.pyfile_ready = function ($) {
// If we're directed to a particular line number, highlight the line.
var frag = location.hash;
@@ -256,6 +258,22 @@ coverage.pyfile_ready = function ($) {
$(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");});
$(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");});
+ coverage.filters = undefined;
+ try {
+ coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
+ } catch(err) {}
+
+ if (coverage.filters) {
+ coverage.filters = JSON.parse(coverage.filters);
+ }
+ else {
+ coverage.filters = {run: false, exc: true, mis: true, par: true};
+ }
+
+ for (cls in coverage.filters) {
+ coverage.set_line_visibilty(cls, coverage.filters[cls]);
+ }
+
coverage.assign_shortkeys();
coverage.wire_up_help_panel();
@@ -266,17 +284,26 @@ coverage.pyfile_ready = function ($) {
};
coverage.toggle_lines = function (btn, cls) {
- btn = $(btn);
- var show = "show_"+cls;
- if (btn.hasClass(show)) {
- $("#source ." + cls).removeClass(show);
- btn.removeClass(show);
- }
- else {
+ var onoff = !$(btn).hasClass("show_" + cls);
+ coverage.set_line_visibilty(cls, onoff);
+ coverage.build_scroll_markers();
+ coverage.filters[cls] = onoff;
+ try {
+ localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
+ } catch(err) {}
+};
+
+coverage.set_line_visibilty = function (cls, onoff) {
+ var show = "show_" + cls;
+ var btn = $(".button_toggle_" + cls);
+ if (onoff) {
$("#source ." + cls).addClass(show);
btn.addClass(show);
}
- coverage.build_scroll_markers();
+ else {
+ $("#source ." + cls).removeClass(show);
+ btn.removeClass(show);
+ }
};
// Return the nth line div.
diff --git a/doc/sample_html/index.html b/doc/sample_html/index.html
index 6ce866387..ac2efcf0b 100644
--- a/doc/sample_html/index.html
+++ b/doc/sample_html/index.html
@@ -156,8 +156,8 @@ Coverage report:
diff --git a/doc/sample_html/status.json b/doc/sample_html/status.json
index 83336a370..daee1db4e 100644
--- a/doc/sample_html/status.json
+++ b/doc/sample_html/status.json
@@ -1 +1 @@
-{"format":2,"version":"5.4","globals":"a486ca194f909abc39de9a093fa5a484","files":{"cogapp___init___py":{"hash":"6010eef3af87123028eb691d70094593","index":{"nums":[1,2,0,0,0,0,0],"html_filename":"cogapp___init___py.html","relative_filename":"cogapp/__init__.py"}},"cogapp___main___py":{"hash":"2cec3551dfd9a5818a6550318658ccd4","index":{"nums":[1,3,0,3,0,0,0],"html_filename":"cogapp___main___py.html","relative_filename":"cogapp/__main__.py"}},"cogapp_backward_py":{"hash":"f95e44a818c73b2187e6fadc6257f8ce","index":{"nums":[1,22,0,6,4,2,2],"html_filename":"cogapp_backward_py.html","relative_filename":"cogapp/backward.py"}},"cogapp_cogapp_py":{"hash":"f85acbdbacefaccb9c499ef6cbe2ffc4","index":{"nums":[1,485,1,215,200,28,132],"html_filename":"cogapp_cogapp_py.html","relative_filename":"cogapp/cogapp.py"}},"cogapp_makefiles_py":{"hash":"4fd2add44238312a5567022fe28737de","index":{"nums":[1,27,0,20,14,0,14],"html_filename":"cogapp_makefiles_py.html","relative_filename":"cogapp/makefiles.py"}},"cogapp_test_cogapp_py":{"hash":"ee9b3c832eaa47b9e3940133c58827af","index":{"nums":[1,790,6,549,20,0,18],"html_filename":"cogapp_test_cogapp_py.html","relative_filename":"cogapp/test_cogapp.py"}},"cogapp_test_makefiles_py":{"hash":"66093f767a400ce1720b94a7371de48b","index":{"nums":[1,71,0,53,6,0,6],"html_filename":"cogapp_test_makefiles_py.html","relative_filename":"cogapp/test_makefiles.py"}},"cogapp_test_whiteutils_py":{"hash":"068beefb2872fe6739fad2471c36a4f1","index":{"nums":[1,69,0,50,0,0,0],"html_filename":"cogapp_test_whiteutils_py.html","relative_filename":"cogapp/test_whiteutils.py"}},"cogapp_whiteutils_py":{"hash":"b16b0e7f940175106b11230fea9e8c8c","index":{"nums":[1,45,0,5,34,4,4],"html_filename":"cogapp_whiteutils_py.html","relative_filename":"cogapp/whiteutils.py"}}}}
\ No newline at end of file
+{"format":2,"version":"5.5","globals":"a486ca194f909abc39de9a093fa5a484","files":{"cogapp___init___py":{"hash":"6010eef3af87123028eb691d70094593","index":{"nums":[1,2,0,0,0,0,0],"html_filename":"cogapp___init___py.html","relative_filename":"cogapp/__init__.py"}},"cogapp___main___py":{"hash":"2cec3551dfd9a5818a6550318658ccd4","index":{"nums":[1,3,0,3,0,0,0],"html_filename":"cogapp___main___py.html","relative_filename":"cogapp/__main__.py"}},"cogapp_backward_py":{"hash":"f95e44a818c73b2187e6fadc6257f8ce","index":{"nums":[1,22,0,6,4,2,2],"html_filename":"cogapp_backward_py.html","relative_filename":"cogapp/backward.py"}},"cogapp_cogapp_py":{"hash":"f85acbdbacefaccb9c499ef6cbe2ffc4","index":{"nums":[1,485,1,215,200,28,132],"html_filename":"cogapp_cogapp_py.html","relative_filename":"cogapp/cogapp.py"}},"cogapp_makefiles_py":{"hash":"4fd2add44238312a5567022fe28737de","index":{"nums":[1,27,0,20,14,0,14],"html_filename":"cogapp_makefiles_py.html","relative_filename":"cogapp/makefiles.py"}},"cogapp_test_cogapp_py":{"hash":"ee9b3c832eaa47b9e3940133c58827af","index":{"nums":[1,790,6,549,20,0,18],"html_filename":"cogapp_test_cogapp_py.html","relative_filename":"cogapp/test_cogapp.py"}},"cogapp_test_makefiles_py":{"hash":"66093f767a400ce1720b94a7371de48b","index":{"nums":[1,71,0,53,6,0,6],"html_filename":"cogapp_test_makefiles_py.html","relative_filename":"cogapp/test_makefiles.py"}},"cogapp_test_whiteutils_py":{"hash":"068beefb2872fe6739fad2471c36a4f1","index":{"nums":[1,69,0,50,0,0,0],"html_filename":"cogapp_test_whiteutils_py.html","relative_filename":"cogapp/test_whiteutils.py"}},"cogapp_whiteutils_py":{"hash":"b16b0e7f940175106b11230fea9e8c8c","index":{"nums":[1,45,0,5,34,4,4],"html_filename":"cogapp_whiteutils_py.html","relative_filename":"cogapp/whiteutils.py"}}}}
\ No newline at end of file
diff --git a/doc/sample_html/style.css b/doc/sample_html/style.css
index 3e7f9b66b..36ee2a6e6 100644
--- a/doc/sample_html/style.css
+++ b/doc/sample_html/style.css
@@ -34,7 +34,7 @@ a.nav:hover { text-decoration: underline; color: inherit; }
@media (prefers-color-scheme: dark) { #header { border-color: #333; } }
-.indexfile #footer { margin: 1rem 3rem; }
+.indexfile #footer { margin: 1rem 3.5rem; }
.pyfile #footer { margin: 1rem 1rem; }
@@ -42,9 +42,9 @@ a.nav:hover { text-decoration: underline; color: inherit; }
@media (prefers-color-scheme: dark) { #footer .content { color: #aaa; } }
-#index { margin: 1rem 0 0 3rem; }
+#index { margin: 1rem 0 0 3.5rem; }
-#header .content { padding: 1rem 3rem; }
+#header .content { padding: 1rem 3.5rem; }
h1 { font-size: 1.25em; display: inline-block; }
@@ -122,13 +122,13 @@ h2.stats { margin-top: .5em; font-size: 1em; }
.keyhelp .key { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; }
-#source { padding: 1em 0 1em 3rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
+#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
#source p { position: relative; white-space: pre; }
#source p * { box-sizing: border-box; }
-#source p .n { float: left; text-align: right; width: 3rem; box-sizing: border-box; margin-left: -3rem; padding-right: 1em; color: #999; }
+#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; }
@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }
diff --git a/howto.txt b/howto.txt
index 8a912833d..aae6c47d1 100644
--- a/howto.txt
+++ b/howto.txt
@@ -45,12 +45,10 @@
$ make publish
- Kits:
- Manually trigger the kit GitHub Action
- - https://github.com/nedbat/coveragepy/actions?query=workflow%3A%22Build+kits%22
- - Download built kits from GitHub Actions:
- $ make clean download_kits
+ - https://github.com/nedbat/coveragepy/actions/workflows/kit.yml
+ - Download and check built kits from GitHub Actions:
+ $ make clean download_kits check_kits
- examine the dist directory, and remove anything that looks malformed.
- - check the dist directory:
- $ python -m twine check dist/*
- test the pypi upload:
$ make test_upload
- Update PyPI:
diff --git a/igor.py b/igor.py
index b2dc05cfe..3c6afa667 100644
--- a/igor.py
+++ b/igor.py
@@ -20,7 +20,11 @@
import warnings
import zipfile
-import pytest
+try:
+ import pytest
+except ImportError:
+ # We want to be able to run this for some tasks that don't need pytest.
+ pytest = None
# Contants derived the same as in coverage/env.py. We can't import
# that file here, it would be evaluated too early and not get the
@@ -157,7 +161,7 @@ def run_tests_with_coverage(tracer, *runner_args):
try:
# Re-import coverage to get it coverage tested! I don't understand all
# the mechanics here, but if I don't carry over the imported modules
- # (in covmods), then things go haywire (os == None, eventually).
+ # (in covmods), then things go haywire (os is None, eventually).
covmods = {}
covdir = os.path.split(coverage.__file__)[0]
# We have to make a list since we'll be deleting in the loop.
@@ -383,7 +387,7 @@ def analyze_args(function):
getargspec = inspect.getargspec
with ignore_warnings():
# DeprecationWarning: Use inspect.signature() instead of inspect.getfullargspec()
- argspec = getargspec(function)
+ argspec = getargspec(function) # pylint: disable=deprecated-method
return bool(argspec[1]), len(argspec[0])
diff --git a/metacov.ini b/metacov.ini
index daabbf82f..47ed31344 100644
--- a/metacov.ini
+++ b/metacov.ini
@@ -49,6 +49,8 @@ exclude_lines =
# Lines that we can't run during metacov.
pragma: no metacov
+ pytest.mark.skipif\(env.METACOV
+ if not env.METACOV:
# These lines only happen if tests fail.
raise AssertionError
@@ -76,8 +78,7 @@ partial_branches =
if .* env.JYTHON
if .* env.IRONPYTHON
-ignore_errors = true
-precision = 1
+precision = 2
[paths]
source =
@@ -86,3 +87,7 @@ source =
*/coverage/trunk
*\coveragepy
/io
+ # GitHub Actions on Ubuntu uses /home/runner/work/coveragepy
+ # GitHub Actions on Mac uses /Users/runner/work/coveragepy
+ # GitHub Actions on Window uses D:\a\coveragepy\coveragepy
+ */coveragepy
diff --git a/pylintrc b/pylintrc
index d250e9b92..c55b89822 100644
--- a/pylintrc
+++ b/pylintrc
@@ -66,6 +66,7 @@ disable=
global-statement,
broad-except,
no-else-return,
+ misplaced-comparison-constant,
# Messages that may be silly:
no-self-use,
no-member,
@@ -83,6 +84,8 @@ disable=
bad-continuation,
# Disable while we still support Python 2:
useless-object-inheritance,
+ super-with-arguments,
+ raise-missing-from,
# Messages that are noisy for now, eventually maybe we'll turn them on:
invalid-name,
protected-access,
diff --git a/requirements/ci.pip b/requirements/ci.pip
index 060d1de3f..72c6a7907 100644
--- a/requirements/ci.pip
+++ b/requirements/ci.pip
@@ -1,6 +1,8 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+-c pins.pip
+
# Things CI servers need for running tests.
-r tox.pip
-r pytest.pip
diff --git a/requirements/dev.pip b/requirements/dev.pip
index 2cd0fe0e3..791a2faed 100644
--- a/requirements/dev.pip
+++ b/requirements/dev.pip
@@ -14,11 +14,15 @@ pluggy==0.13.1
# for linting.
greenlet==0.4.16
-pylint==2.5.3
-check-manifest==0.42
+astroid==2.5
+pylint==2.7.1
+check-manifest==0.46
readme_renderer==26.0
# for kitting.
-requests==2.24.0
-twine==3.2.0
-libsass==0.20.0
+requests==2.25.1
+twine==3.3.0
+libsass==0.20.1
+
+# Just so I have a debugger if I want it
+pudb==2019.2
diff --git a/requirements/pins.pip b/requirements/pins.pip
index 223e7cbdf..04721c8bb 100644
--- a/requirements/pins.pip
+++ b/requirements/pins.pip
@@ -5,3 +5,7 @@
cibuildwheel==1.7.0
tox-gh-actions==2.2.0
+
+# setuptools 45.x is py3-only
+setuptools==44.1.1
+wheel==0.35.1
diff --git a/requirements/pytest.pip b/requirements/pytest.pip
index 43d4efe51..ecdf619c0 100644
--- a/requirements/pytest.pip
+++ b/requirements/pytest.pip
@@ -19,6 +19,3 @@ hypothesis==4.57.1
# Our testing mixins
unittest-mixins==1.6
#-e/Users/ned/unittest_mixins
-
-# Just so I have a debugger if I want it
-pudb==2019.2
diff --git a/requirements/wheel.pip b/requirements/wheel.pip
index ae84163ab..f294ab3bf 100644
--- a/requirements/wheel.pip
+++ b/requirements/wheel.pip
@@ -1,8 +1,9 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+-c pins.pip
+
# Things needed to make wheels for coverage.py
-# setuptools 45.x is py3-only
-setuptools==44.1.1
-wheel==0.35.1
+setuptools
+wheel
diff --git a/tests/conftest.py b/tests/conftest.py
index 10761cddf..81ec9f775 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,6 +16,11 @@
from coverage import env
+# Pytest will rewrite assertions in test modules, but not elsewhere.
+# This tells pytest to also rewrite assertions in coveragetest.py.
+pytest.register_assert_rewrite("tests.coveragetest")
+pytest.register_assert_rewrite("tests.helpers")
+
# Pytest can take additional options:
# $set_env.py: PYTEST_ADDOPTS - Extra arguments to pytest.
@@ -77,7 +82,7 @@ def fix_xdist_sys_path():
See: https://github.com/pytest-dev/pytest-xdist/issues/376
"""
- if os.environ.get('PYTEST_XDIST_WORKER', ''):
+ if os.environ.get('PYTEST_XDIST_WORKER', ''): # pragma: part covered
# We are running in an xdist worker.
if sys.path[1] == '':
# xdist has set sys.path[1] to ''. Clobber it.
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index dbadd226b..3363fa894 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -5,7 +5,7 @@
import contextlib
import datetime
-import functools
+import difflib
import glob
import os
import os.path
@@ -13,23 +13,19 @@
import re
import shlex
import sys
-import types
+import unittest
import pytest
-from unittest_mixins import (
- EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin,
- DelayedAssertionMixin,
-)
+from unittest_mixins import EnvironmentAwareMixin, TempDirMixin
import coverage
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.misc import StopEverything
-from tests.helpers import arcs_to_arcz_repr, arcz_to_arcs
+from tests.helpers import arcs_to_arcz_repr, arcz_to_arcs, assert_count_equal
from tests.helpers import run_command, SuperModuleCleaner
+from tests.mixins import StdStreamCapturingMixin, StopEverythingMixin
# Status returns for the command line.
@@ -39,37 +35,12 @@
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):
- try:
- result = method(*args, **kwargs)
- except StopEverything:
- raise unittest.SkipTest("StopEverything!")
- return result
- return _wrapper
-
-
-class SkipConvertingMetaclass(type):
- """Decorate all test methods to convert StopEverything to SkipTest."""
- def __new__(cls, name, bases, attrs):
- for attr_name, attr_value in attrs.items():
- if attr_name.startswith('test_') and isinstance(attr_value, types.FunctionType):
- attrs[attr_name] = convert_skip_exceptions(attr_value)
-
- return super(SkipConvertingMetaclass, cls).__new__(cls, name, bases, attrs)
-
-
-CoverageTestMethodsMixin = SkipConvertingMetaclass('CoverageTestMethodsMixin', (), {})
-
class CoverageTest(
EnvironmentAwareMixin,
StdStreamCapturingMixin,
TempDirMixin,
- DelayedAssertionMixin,
- CoverageTestMethodsMixin,
- TestCase,
+ StopEverythingMixin,
+ unittest.TestCase,
):
"""A base class for coverage.py test cases."""
@@ -84,7 +55,7 @@ class CoverageTest(
# Temp dirs go to $TMPDIR/coverage_test/*
temp_dir_prefix = "coverage_test/"
- if os.getenv('COVERAGE_ENV_ID'):
+ if os.getenv('COVERAGE_ENV_ID'): # pragma: debugging
temp_dir_prefix += "{}/".format(os.getenv('COVERAGE_ENV_ID'))
# Keep the temp directories if the env says to.
@@ -134,17 +105,27 @@ def get_module_name(self):
self.last_module_name = 'coverage_test_' + str(random.random())[2:]
return self.last_module_name
- def assert_equal_arcs(self, a1, a2, msg=None):
- """Assert that the arc lists `a1` and `a2` are equal."""
+ def _check_arcs(self, a1, a2, arc_type):
+ """Check that the arc lists `a1` and `a2` are equal.
+
+ If they are equal, return empty string. If they are unequal, return
+ a string explaining what is different.
+ """
# Make them into multi-line strings so we can see what's going wrong.
s1 = arcs_to_arcz_repr(a1)
s2 = arcs_to_arcz_repr(a2)
- self.assertMultiLineEqual(s1, s2, msg)
+ if s1 != s2:
+ lines1 = s1.splitlines(True)
+ lines2 = s2.splitlines(True)
+ diff = "".join(difflib.ndiff(lines1, lines2))
+ return "\n" + arc_type + " arcs differ: minus is expected, plus is actual\n" + diff
+ else:
+ return ""
def check_coverage(
self, text, lines=None, missing="", report="",
excludes=None, partials="",
- arcz=None, arcz_missing="", arcz_unpredicted="",
+ arcz=None, arcz_missing=None, arcz_unpredicted=None,
arcs=None, arcs_missing=None, arcs_unpredicted=None,
):
"""Check the coverage measurement of `text`.
@@ -173,9 +154,9 @@ def check_coverage(
if arcs is None and arcz is not None:
arcs = arcz_to_arcs(arcz)
- if arcs_missing is None:
+ if arcs_missing is None and arcz_missing is not None:
arcs_missing = arcz_to_arcs(arcz_missing)
- if arcs_unpredicted is None:
+ if arcs_unpredicted is None and arcz_unpredicted is not None:
arcs_unpredicted = arcz_to_arcs(arcz_unpredicted)
# Start up coverage.py.
@@ -198,7 +179,7 @@ def check_coverage(
if isinstance(lines[0], int):
# lines is just a list of numbers, it must match the statements
# found in the code.
- self.assertEqual(statements, lines)
+ assert statements == lines, "{!r} != {!r}".format(statements, lines)
else:
# lines is a list of possible line number lists, one of them
# must match.
@@ -210,7 +191,8 @@ def check_coverage(
missing_formatted = analysis.missing_formatted()
if isinstance(missing, string_class):
- self.assertEqual(missing_formatted, missing)
+ msg = "{!r} != {!r}".format(missing_formatted, missing)
+ assert missing_formatted == missing, msg
else:
for missing_list in missing:
if missing_formatted == missing_list:
@@ -224,27 +206,20 @@ def check_coverage(
# print(" actual:", analysis.arc_possibilities())
# print("Executed:")
# print(" actual:", sorted(set(analysis.arcs_executed())))
- with self.delayed_assertions():
- self.assert_equal_arcs(
- arcs, analysis.arc_possibilities(),
- "Possible arcs differ: minus is expected, plus is actual"
- )
-
- self.assert_equal_arcs(
- arcs_missing, analysis.arcs_missing(),
- "Missing arcs differ: minus is expected, plus is actual"
- )
-
- self.assert_equal_arcs(
- arcs_unpredicted, analysis.arcs_unpredicted(),
- "Unpredicted arcs differ: minus is expected, plus is actual"
- )
+ # TODO: this would be nicer with pytest-check, once we can run that.
+ msg = (
+ self._check_arcs(arcs, analysis.arc_possibilities(), "Possible") +
+ self._check_arcs(arcs_missing, analysis.arcs_missing(), "Missing") +
+ self._check_arcs(arcs_unpredicted, analysis.arcs_unpredicted(), "Unpredicted")
+ )
+ if msg:
+ assert False, msg
if report:
frep = StringIO()
cov.report(mod, file=frep, show_missing=True)
rep = " ".join(frep.getvalue().split("\n")[2].split()[1:])
- self.assertEqual(report, rep)
+ assert report == rep, "{!r} != {!r}".format(report, rep)
return cov
@@ -308,24 +283,24 @@ def assert_same_files(self, flist1, flist2):
"""Assert that `flist1` and `flist2` are the same set of file names."""
flist1_nice = [self.nice_file(f) for f in flist1]
flist2_nice = [self.nice_file(f) for f in flist2]
- self.assertCountEqual(flist1_nice, flist2_nice)
+ assert_count_equal(flist1_nice, flist2_nice)
def assert_exists(self, fname):
"""Assert that `fname` is a file that exists."""
msg = "File %r should exist" % fname
- self.assertTrue(os.path.exists(fname), msg)
+ assert os.path.exists(fname), msg
def assert_doesnt_exist(self, fname):
"""Assert that `fname` is a file that doesn't exist."""
msg = "File %r shouldn't exist" % fname
- self.assertTrue(not os.path.exists(fname), msg)
+ assert not os.path.exists(fname), msg
def assert_file_count(self, pattern, count):
"""Assert that there are `count` files matching `pattern`."""
files = sorted(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)
+ assert len(files) == count, msg
def assert_starts_with(self, s, prefix, msg=None):
"""Assert that `s` starts with `prefix`."""
@@ -335,8 +310,8 @@ def assert_starts_with(self, s, prefix, msg=None):
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
- self.assertGreaterEqual(age.total_seconds(), 0, msg)
- self.assertLessEqual(age.total_seconds(), seconds, msg)
+ assert age.total_seconds() >= 0, msg
+ assert age.total_seconds() <= seconds, msg
def command_line(self, args, ret=OK):
"""Run `args` through the command line.
@@ -351,7 +326,7 @@ def command_line(self, args, ret=OK):
"""
ret_actual = command_line(args)
- self.assertEqual(ret_actual, ret)
+ assert ret_actual == ret, "{!r} != {!r}".format(ret_actual, ret)
# Some distros rename the coverage command, and need a way to indicate
# their new command name to the tests. This is here for them to override,
@@ -414,7 +389,7 @@ def run_command_status(self, cmd):
if env.JYTHON: # pragma: only jython
# Jython can't do reporting, so let's skip the test now.
if command_args and command_args[0] in ('report', 'html', 'xml', 'annotate'):
- self.skipTest("Can't run reporting commands in Jython")
+ pytest.skip("Can't run reporting commands in Jython")
# Jython can't run "coverage" as a command because the shebang
# refers to another shebang'd Python script. So run them as
# modules.
@@ -454,13 +429,13 @@ def working_root(self):
def report_from_command(self, cmd):
"""Return the report from the `cmd`, with some convenience added."""
report = self.run_command(cmd).replace('\\', '/')
- self.assertNotIn("error", report.lower())
+ assert "error" not in report.lower()
return report
def report_lines(self, report):
"""Return the lines of the report, as a list."""
lines = report.split('\n')
- self.assertEqual(lines[-1], "")
+ assert lines[-1] == ""
return lines[:-1]
def line_count(self, report):
diff --git a/tests/gold/html/styled/style.css b/tests/gold/html/styled/style.css
index 3e7f9b66b..36ee2a6e6 100644
--- a/tests/gold/html/styled/style.css
+++ b/tests/gold/html/styled/style.css
@@ -34,7 +34,7 @@ a.nav:hover { text-decoration: underline; color: inherit; }
@media (prefers-color-scheme: dark) { #header { border-color: #333; } }
-.indexfile #footer { margin: 1rem 3rem; }
+.indexfile #footer { margin: 1rem 3.5rem; }
.pyfile #footer { margin: 1rem 1rem; }
@@ -42,9 +42,9 @@ a.nav:hover { text-decoration: underline; color: inherit; }
@media (prefers-color-scheme: dark) { #footer .content { color: #aaa; } }
-#index { margin: 1rem 0 0 3rem; }
+#index { margin: 1rem 0 0 3.5rem; }
-#header .content { padding: 1rem 3rem; }
+#header .content { padding: 1rem 3.5rem; }
h1 { font-size: 1.25em; display: inline-block; }
@@ -122,13 +122,13 @@ h2.stats { margin-top: .5em; font-size: 1em; }
.keyhelp .key { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; }
-#source { padding: 1em 0 1em 3rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
+#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
#source p { position: relative; white-space: pre; }
#source p * { box-sizing: border-box; }
-#source p .n { float: left; text-align: right; width: 3rem; box-sizing: border-box; margin-left: -3rem; padding-right: 1em; color: #999; }
+#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; }
@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }
diff --git a/tests/helpers.py b/tests/helpers.py
index 0621d7a94..a96b793ef 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -3,6 +3,7 @@
"""Helpers for coverage.py tests."""
+import collections
import glob
import os
import re
@@ -197,7 +198,7 @@ def arcs_to_arcz_repr(arcs):
"""
repr_list = []
- for a, b in arcs:
+ for a, b in (arcs or ()):
line = repr((a, b))
line += " # "
line += _arcs_to_arcz_repr_one(a)
@@ -222,3 +223,13 @@ def without_module(using_module, missing_module_name):
"""
return mock.patch.object(using_module, missing_module_name, None)
+
+
+def assert_count_equal(a, b):
+ """
+ A pytest-friendly implementation of assertCountEqual.
+
+ Assert that `a` and `b` have the same elements, but maybe in different order.
+ This only works for hashable elements.
+ """
+ assert collections.Counter(list(a)) == collections.Counter(list(b))
diff --git a/tests/mixins.py b/tests/mixins.py
new file mode 100644
index 000000000..9d096d4d5
--- /dev/null
+++ b/tests/mixins.py
@@ -0,0 +1,70 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""
+Test class mixins
+
+Some of these are transitional while working toward pure-pytest style.
+"""
+
+import functools
+import types
+import unittest
+
+import pytest
+
+from coverage.misc import StopEverything
+
+
+def convert_skip_exceptions(method):
+ """A decorator for test methods to convert StopEverything to SkipTest."""
+ @functools.wraps(method)
+ def _wrapper(*args, **kwargs):
+ try:
+ result = method(*args, **kwargs)
+ except StopEverything:
+ raise unittest.SkipTest("StopEverything!")
+ return result
+ return _wrapper
+
+
+class SkipConvertingMetaclass(type):
+ """Decorate all test methods to convert StopEverything to SkipTest."""
+ def __new__(cls, name, bases, attrs):
+ for attr_name, attr_value in attrs.items():
+ if attr_name.startswith('test_') and isinstance(attr_value, types.FunctionType):
+ attrs[attr_name] = convert_skip_exceptions(attr_value)
+
+ return super(SkipConvertingMetaclass, cls).__new__(cls, name, bases, attrs)
+
+
+StopEverythingMixin = SkipConvertingMetaclass('StopEverythingMixin', (), {})
+
+
+class StdStreamCapturingMixin:
+ """
+ Adapter from the pytest capsys fixture to more convenient methods.
+
+ This doesn't also output to the real stdout, so we probably want to move
+ to "real" capsys when we can use fixtures in test methods.
+
+ Once you've used one of these methods, the capturing is reset, so another
+ invocation will only return the delta.
+
+ """
+ @pytest.fixture(autouse=True)
+ def _capcapsys(self, capsys):
+ """Grab the fixture so our methods can use it."""
+ self.capsys = capsys
+
+ def stdouterr(self):
+ """Returns (out, err), two strings for stdout and stderr."""
+ return self.capsys.readouterr()
+
+ def stdout(self):
+ """Returns a string, the captured stdout."""
+ return self.capsys.readouterr().out
+
+ def stderr(self):
+ """Returns a string, the captured stderr."""
+ return self.capsys.readouterr().err
diff --git a/tests/test_api.py b/tests/test_api.py
index f8b7b4b2c..391a52e0b 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -12,6 +12,7 @@
import sys
import textwrap
+import pytest
from unittest_mixins import change_dir
import coverage
@@ -21,7 +22,8 @@
from coverage.files import abs_file, relative_filename
from coverage.misc import CoverageException
-from tests.coveragetest import CoverageTest, CoverageTestMethodsMixin, TESTS_DIR, UsingModulesMixin
+from tests.coveragetest import CoverageTest, StopEverythingMixin, TESTS_DIR, UsingModulesMixin
+from tests.helpers import assert_count_equal
class ApiTest(CoverageTest):
@@ -42,7 +44,7 @@ def assertFiles(self, files):
"""Assert that the files here are `files`, ignoring the usual junk."""
here = os.listdir(".")
here = self.clean_files(here, ["*.pyc", "__pycache__", "*$py.class"])
- self.assertCountEqual(here, files)
+ assert_count_equal(here, files)
def test_unexecuted_file(self):
cov = coverage.Coverage()
@@ -63,8 +65,8 @@ def test_unexecuted_file(self):
self.start_import_stop(cov, "mycode")
_, statements, missing, _ = cov.analysis("not_run.py")
- self.assertEqual(statements, [1])
- self.assertEqual(missing, [1])
+ assert statements == [1]
+ assert missing == [1]
def test_filenames(self):
@@ -82,14 +84,14 @@ def test_filenames(self):
self.start_import_stop(cov, "mymain")
filename, _, _, _ = cov.analysis("mymain.py")
- self.assertEqual(os.path.basename(filename), "mymain.py")
+ assert os.path.basename(filename) == "mymain.py"
filename, _, _, _ = cov.analysis("mymod.py")
- self.assertEqual(os.path.basename(filename), "mymod.py")
+ assert os.path.basename(filename) == "mymod.py"
filename, _, _, _ = cov.analysis(sys.modules["mymain"])
- self.assertEqual(os.path.basename(filename), "mymain.py")
+ assert os.path.basename(filename) == "mymain.py"
filename, _, _, _ = cov.analysis(sys.modules["mymod"])
- self.assertEqual(os.path.basename(filename), "mymod.py")
+ assert os.path.basename(filename) == "mymod.py"
# Import the Python file, executing it again, once it's been compiled
# already.
@@ -97,14 +99,14 @@ def test_filenames(self):
self.start_import_stop(cov, "mymain")
filename, _, _, _ = cov.analysis("mymain.py")
- self.assertEqual(os.path.basename(filename), "mymain.py")
+ assert os.path.basename(filename) == "mymain.py"
filename, _, _, _ = cov.analysis("mymod.py")
- self.assertEqual(os.path.basename(filename), "mymod.py")
+ assert os.path.basename(filename) == "mymod.py"
filename, _, _, _ = cov.analysis(sys.modules["mymain"])
- self.assertEqual(os.path.basename(filename), "mymain.py")
+ assert os.path.basename(filename) == "mymain.py"
filename, _, _, _ = cov.analysis(sys.modules["mymod"])
- self.assertEqual(os.path.basename(filename), "mymod.py")
+ assert os.path.basename(filename) == "mymod.py"
def test_ignore_stdlib(self):
self.make_file("mymain.py", """\
@@ -115,15 +117,15 @@ def test_ignore_stdlib(self):
# Measure without the stdlib.
cov1 = coverage.Coverage()
- self.assertEqual(cov1.config.cover_pylib, False)
+ assert cov1.config.cover_pylib is False
self.start_import_stop(cov1, "mymain")
# some statements were marked executed in mymain.py
_, statements, missing, _ = cov1.analysis("mymain.py")
- self.assertNotEqual(statements, missing)
+ assert statements != missing
# but none were in colorsys.py
_, statements, missing, _ = cov1.analysis("colorsys.py")
- self.assertEqual(statements, missing)
+ assert statements == missing
# Measure with the stdlib.
cov2 = coverage.Coverage(cover_pylib=True)
@@ -131,10 +133,10 @@ def test_ignore_stdlib(self):
# some statements were marked executed in mymain.py
_, statements, missing, _ = cov2.analysis("mymain.py")
- self.assertNotEqual(statements, missing)
+ assert statements != missing
# and some were marked executed in colorsys.py
_, statements, missing, _ = cov2.analysis("colorsys.py")
- self.assertNotEqual(statements, missing)
+ assert statements != missing
def test_include_can_measure_stdlib(self):
self.make_file("mymain.py", """\
@@ -150,57 +152,55 @@ def test_include_can_measure_stdlib(self):
# some statements were marked executed in colorsys.py
_, statements, missing, _ = cov1.analysis("colorsys.py")
- self.assertNotEqual(statements, missing)
+ assert statements != missing
# but none were in random.py
_, statements, missing, _ = cov1.analysis("random.py")
- self.assertEqual(statements, missing)
+ assert statements == missing
def test_exclude_list(self):
cov = coverage.Coverage()
cov.clear_exclude()
- self.assertEqual(cov.get_exclude_list(), [])
+ assert cov.get_exclude_list() == []
cov.exclude("foo")
- self.assertEqual(cov.get_exclude_list(), ["foo"])
+ assert cov.get_exclude_list() == ["foo"]
cov.exclude("bar")
- self.assertEqual(cov.get_exclude_list(), ["foo", "bar"])
- self.assertEqual(cov._exclude_regex('exclude'), "(?:foo)|(?:bar)")
+ assert cov.get_exclude_list() == ["foo", "bar"]
+ assert cov._exclude_regex('exclude') == "(?:foo)|(?:bar)"
cov.clear_exclude()
- self.assertEqual(cov.get_exclude_list(), [])
+ assert cov.get_exclude_list() == []
def test_exclude_partial_list(self):
cov = coverage.Coverage()
cov.clear_exclude(which='partial')
- self.assertEqual(cov.get_exclude_list(which='partial'), [])
+ assert cov.get_exclude_list(which='partial') == []
cov.exclude("foo", which='partial')
- self.assertEqual(cov.get_exclude_list(which='partial'), ["foo"])
+ assert cov.get_exclude_list(which='partial') == ["foo"]
cov.exclude("bar", which='partial')
- self.assertEqual(cov.get_exclude_list(which='partial'), ["foo", "bar"])
- self.assertEqual(
- cov._exclude_regex(which='partial'), "(?:foo)|(?:bar)"
- )
+ assert cov.get_exclude_list(which='partial') == ["foo", "bar"]
+ assert cov._exclude_regex(which='partial') == "(?:foo)|(?:bar)"
cov.clear_exclude(which='partial')
- self.assertEqual(cov.get_exclude_list(which='partial'), [])
+ assert cov.get_exclude_list(which='partial') == []
def test_exclude_and_partial_are_separate_lists(self):
cov = coverage.Coverage()
cov.clear_exclude(which='partial')
cov.clear_exclude(which='exclude')
cov.exclude("foo", which='partial')
- self.assertEqual(cov.get_exclude_list(which='partial'), ['foo'])
- self.assertEqual(cov.get_exclude_list(which='exclude'), [])
+ assert cov.get_exclude_list(which='partial') == ['foo']
+ assert cov.get_exclude_list(which='exclude') == []
cov.exclude("bar", which='exclude')
- self.assertEqual(cov.get_exclude_list(which='partial'), ['foo'])
- self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar'])
+ assert cov.get_exclude_list(which='partial') == ['foo']
+ assert cov.get_exclude_list(which='exclude') == ['bar']
cov.exclude("p2", which='partial')
cov.exclude("e2", which='exclude')
- self.assertEqual(cov.get_exclude_list(which='partial'), ['foo', 'p2'])
- self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar', 'e2'])
+ assert cov.get_exclude_list(which='partial') == ['foo', 'p2']
+ assert cov.get_exclude_list(which='exclude') == ['bar', 'e2']
cov.clear_exclude(which='partial')
- self.assertEqual(cov.get_exclude_list(which='partial'), [])
- self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar', 'e2'])
+ assert cov.get_exclude_list(which='partial') == []
+ assert cov.get_exclude_list(which='exclude') == ['bar', 'e2']
cov.clear_exclude(which='exclude')
- self.assertEqual(cov.get_exclude_list(which='partial'), [])
- self.assertEqual(cov.get_exclude_list(which='exclude'), [])
+ assert cov.get_exclude_list(which='partial') == []
+ assert cov.get_exclude_list(which='exclude') == []
def test_datafile_default(self):
# Default data file behavior: it's .coverage
@@ -292,7 +292,7 @@ def test_empty_reporting(self):
# empty summary reports raise exception, just like the xml report
cov = coverage.Coverage()
cov.erase()
- with self.assertRaisesRegex(CoverageException, "No data to report."):
+ with pytest.raises(CoverageException, match="No data to report."):
cov.report()
def test_completely_zero_reporting(self):
@@ -310,7 +310,7 @@ def test_completely_zero_reporting(self):
# TOTAL 1 1 0%
last = self.last_line_squeezed(self.stdout())
- self.assertEqual("TOTAL 1 1 0%", last)
+ assert "TOTAL 1 1 0%" == last
def test_cov4_data_file(self):
cov4_data = (
@@ -319,7 +319,7 @@ def test_cov4_data_file(self):
)
self.make_file(".coverage", cov4_data)
cov = coverage.Coverage()
- with self.assertRaisesRegex(CoverageException, "Looks like a coverage 4.x data file"):
+ with pytest.raises(CoverageException, match="Looks like a coverage 4.x data file"):
cov.load()
cov.erase()
@@ -336,11 +336,11 @@ def make_code1_code2(self):
def check_code1_code2(self, cov):
"""Check the analysis is correct for code1.py and code2.py."""
_, statements, missing, _ = cov.analysis("code1.py")
- self.assertEqual(statements, [1])
- self.assertEqual(missing, [])
+ assert statements == [1]
+ assert missing == []
_, statements, missing, _ = cov.analysis("code2.py")
- self.assertEqual(statements, [1, 2])
- self.assertEqual(missing, [])
+ assert statements == [1, 2]
+ assert missing == []
def test_start_stop_start_stop(self):
self.make_code1_code2()
@@ -441,18 +441,18 @@ def test_combining_twice(self):
self.assert_exists(".coverage")
cov2 = coverage.Coverage()
- with self.assertRaisesRegex(CoverageException, r"No data to combine"):
- cov2.combine(strict=True)
+ with pytest.raises(CoverageException, match=r"No data to combine"):
+ cov2.combine(strict=True, keep=False)
cov3 = coverage.Coverage()
cov3.combine()
# Now the data is empty!
_, statements, missing, _ = cov3.analysis("code1.py")
- self.assertEqual(statements, [1])
- self.assertEqual(missing, [1])
+ assert statements == [1]
+ assert missing == [1]
_, statements, missing, _ = cov3.analysis("code2.py")
- self.assertEqual(statements, [1, 2])
- self.assertEqual(missing, [1, 2])
+ assert statements == [1, 2]
+ assert missing == [1, 2]
def test_combining_with_a_used_coverage(self):
# Can you use a coverage object to run one shard of a parallel suite,
@@ -521,16 +521,14 @@ def test_warnings(self):
self.start_import_stop(cov, "hello")
cov.get_data()
- out = self.stdout()
- self.assertIn("Hello\n", out)
-
- err = self.stderr()
- self.assertIn(textwrap.dedent("""\
+ out, err = self.stdouterr()
+ assert "Hello\n" in out
+ assert textwrap.dedent("""\
Coverage.py warning: Module sys has no Python source. (module-not-python)
Coverage.py warning: Module xyzzy was never imported. (module-not-imported)
Coverage.py warning: Module quux was never imported. (module-not-imported)
Coverage.py warning: No data was collected. (no-data-collected)
- """), err)
+ """) in err
def test_warnings_suppressed(self):
self.make_file("hello.py", """\
@@ -545,16 +543,11 @@ def test_warnings_suppressed(self):
self.start_import_stop(cov, "hello")
cov.get_data()
- out = self.stdout()
- self.assertIn("Hello\n", out)
-
- err = self.stderr()
- self.assertIn(
- "Coverage.py warning: Module sys has no Python source. (module-not-python)",
- err
- )
- self.assertNotIn("module-not-imported", err)
- self.assertNotIn("no-data-collected", err)
+ out, err = self.stdouterr()
+ assert "Hello\n" in out
+ assert "Coverage.py warning: Module sys has no Python source. (module-not-python)" in err
+ assert "module-not-imported" not in err
+ assert "no-data-collected" not in err
def test_warn_once(self):
cov = coverage.Coverage()
@@ -562,8 +555,8 @@ def test_warn_once(self):
cov._warn("Warning, warning 1!", slug="bot", once=True)
cov._warn("Warning, warning 2!", slug="bot", once=True)
err = self.stderr()
- self.assertIn("Warning, warning 1!", err)
- self.assertNotIn("Warning, warning 2!", err)
+ assert "Warning, warning 1!" in err
+ assert "Warning, warning 2!" not in err
def test_source_and_include_dont_conflict(self):
# A bad fix made this case fail: https://github.com/nedbat/coveragepy/issues/541
@@ -592,7 +585,7 @@ def test_source_and_include_dont_conflict(self):
---------------------------
TOTAL 1 0 100%
""")
- self.assertEqual(expected, self.stdout())
+ assert expected == self.stdout()
def make_test_files(self):
"""Create a simple file representing a method with two tests.
@@ -637,18 +630,15 @@ def test_switch_context_testrunner(self):
# Labeled data is collected
data = cov.get_data()
- self.assertEqual(
- [u'', u'multiply_six', u'multiply_zero'],
- sorted(data.measured_contexts())
- )
+ assert [u'', u'multiply_six', u'multiply_zero'] == sorted(data.measured_contexts())
filenames = self.get_measured_filenames(data)
suite_filename = filenames['testsuite.py']
data.set_query_context("multiply_six")
- self.assertEqual([2, 8], sorted(data.lines(suite_filename)))
+ assert [2, 8] == sorted(data.lines(suite_filename))
data.set_query_context("multiply_zero")
- self.assertEqual([2, 5], sorted(data.lines(suite_filename)))
+ assert [2, 5] == sorted(data.lines(suite_filename))
def test_switch_context_with_static(self):
# This test simulates a coverage-aware test runner,
@@ -678,18 +668,16 @@ def test_switch_context_with_static(self):
# Labeled data is collected
data = cov.get_data()
- self.assertEqual(
- [u'mysuite', u'mysuite|multiply_six', u'mysuite|multiply_zero'],
- sorted(data.measured_contexts()),
- )
+ expected = [u'mysuite', u'mysuite|multiply_six', u'mysuite|multiply_zero']
+ assert expected == sorted(data.measured_contexts())
filenames = self.get_measured_filenames(data)
suite_filename = filenames['testsuite.py']
data.set_query_context("mysuite|multiply_six")
- self.assertEqual([2, 8], sorted(data.lines(suite_filename)))
+ assert [2, 8] == sorted(data.lines(suite_filename))
data.set_query_context("mysuite|multiply_zero")
- self.assertEqual([2, 5], sorted(data.lines(suite_filename)))
+ assert [2, 5] == sorted(data.lines(suite_filename))
def test_dynamic_context_conflict(self):
cov = coverage.Coverage(source=["."])
@@ -698,24 +686,22 @@ def test_dynamic_context_conflict(self):
# Switch twice, but only get one warning.
cov.switch_context("test1") # pragma: nested
cov.switch_context("test2") # pragma: nested
- self.assertEqual( # pragma: nested
- self.stderr(),
- "Coverage.py warning: Conflicting dynamic contexts (dynamic-conflict)\n"
- )
+ expected = "Coverage.py warning: Conflicting dynamic contexts (dynamic-conflict)\n"
+ assert expected == self.stderr()
cov.stop() # pragma: nested
def test_switch_context_unstarted(self):
# Coverage must be started to switch context
msg = "Cannot switch context, coverage is not started"
cov = coverage.Coverage()
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
cov.switch_context("test1")
cov.start()
cov.switch_context("test2") # pragma: nested
cov.stop() # pragma: nested
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
cov.switch_context("test3")
def test_config_crash(self):
@@ -723,7 +709,7 @@ def test_config_crash(self):
# exceptions from inside Coverage.
cov = coverage.Coverage()
cov.set_option("run:_crash", "test_config_crash")
- with self.assertRaisesRegex(Exception, "Crashing because called by test_config_crash"):
+ with pytest.raises(Exception, match="Crashing because called by test_config_crash"):
cov.start()
def test_config_crash_no_crash(self):
@@ -777,21 +763,19 @@ def test_current(self):
assert cur0 is cur3
+@pytest.mark.skipif(not env.PYBEHAVIOR.namespaces_pep420,
+ reason="Python before 3.3 doesn't have namespace packages"
+)
class NamespaceModuleTest(UsingModulesMixin, CoverageTest):
"""Test PEP-420 namespace modules."""
- def setUp(self):
- if not env.PYBEHAVIOR.namespaces_pep420:
- self.skipTest("Python before 3.3 doesn't have namespace packages")
- super(NamespaceModuleTest, self).setUp()
-
def test_explicit_namespace_module(self):
self.make_file("main.py", "import namespace_420\n")
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
- with self.assertRaisesRegex(CoverageException, r"Module .* has no file"):
+ with pytest.raises(CoverageException, match=r"Module .* has no file"):
cov.analysis(sys.modules['namespace_420'])
def test_bug_572(self):
@@ -805,7 +789,7 @@ def test_bug_572(self):
cov.report()
-class IncludeOmitTestsMixin(UsingModulesMixin, CoverageTestMethodsMixin):
+class IncludeOmitTestsMixin(UsingModulesMixin, StopEverythingMixin):
"""Test methods for coverage methods taking include and omit."""
# We don't write any source files, but the data file will collide with
@@ -815,12 +799,12 @@ class IncludeOmitTestsMixin(UsingModulesMixin, CoverageTestMethodsMixin):
def filenames_in(self, summary, filenames):
"""Assert the `filenames` are in the keys of `summary`."""
for filename in filenames.split():
- self.assertIn(filename, summary)
+ assert filename in summary
def filenames_not_in(self, summary, filenames):
"""Assert the `filenames` are not in the keys of `summary`."""
for filename in filenames.split():
- self.assertNotIn(filename, summary)
+ assert filename not in summary
def test_nothing_specified(self):
result = self.coverage_usepkgs()
@@ -892,27 +876,27 @@ def test_source_include_exclusive(self):
cov.stop() # pragma: nested
def test_source_package_as_package(self):
- self.assertFalse(os.path.isdir("pkg1"))
+ assert not os.path.isdir("pkg1")
lines = self.coverage_usepkgs(source=["pkg1"])
self.filenames_in(lines, "p1a p1b")
self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
# Because source= was specified, we do search for unexecuted files.
- self.assertEqual(lines['p1c'], 0)
+ assert lines['p1c'] == 0
def test_source_package_as_dir(self):
self.chdir(self.nice_file(TESTS_DIR, 'modules'))
- self.assertTrue(os.path.isdir("pkg1"))
+ assert os.path.isdir("pkg1")
lines = self.coverage_usepkgs(source=["pkg1"])
self.filenames_in(lines, "p1a p1b")
self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
# Because source= was specified, we do search for unexecuted files.
- self.assertEqual(lines['p1c'], 0)
+ assert lines['p1c'] == 0
def test_source_package_dotted_sub(self):
lines = self.coverage_usepkgs(source=["pkg1.sub"])
self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
# Because source= was specified, we do search for unexecuted files.
- self.assertEqual(lines['runmod3'], 0)
+ assert lines['runmod3'] == 0
def test_source_package_dotted_p1b(self):
lines = self.coverage_usepkgs(source=["pkg1.p1b"])
@@ -930,14 +914,14 @@ def test_source_package_part_omitted(self):
lines = self.coverage_usepkgs(source=["pkg1"], omit=["pkg1/p1b.py"])
self.filenames_in(lines, "p1a")
self.filenames_not_in(lines, "p1b")
- self.assertEqual(lines['p1c'], 0)
+ assert lines['p1c'] == 0
def test_source_package_as_package_part_omitted(self):
# https://github.com/nedbat/coveragepy/issues/638
lines = self.coverage_usepkgs(source=["pkg1"], omit=["*/p1b.py"])
self.filenames_in(lines, "p1a")
self.filenames_not_in(lines, "p1b")
- self.assertEqual(lines['p1c'], 0)
+ assert lines['p1c'] == 0
def test_ambiguous_source_package_as_dir(self):
# pkg1 is a directory and a pkg, since we cd into tests/modules/ambiguous
@@ -954,7 +938,7 @@ def test_ambiguous_source_package_as_package(self):
self.filenames_in(lines, "p1a p1b")
self.filenames_not_in(lines, "p2a p2b othera otherb osa osb ambiguous")
# Because source= was specified, we do search for unexecuted files.
- self.assertEqual(lines['p1c'], 0)
+ assert lines['p1c'] == 0
class ReportIncludeOmitTest(IncludeOmitTestsMixin, CoverageTest):
@@ -1012,13 +996,13 @@ def fun2(x):
self.start_import_stop(cov, "missing")
nums = cov._analyze("missing.py").numbers
- self.assertEqual(nums.n_files, 1)
- self.assertEqual(nums.n_statements, 7)
- self.assertEqual(nums.n_excluded, 1)
- self.assertEqual(nums.n_missing, 3)
- self.assertEqual(nums.n_branches, 2)
- self.assertEqual(nums.n_partial_branches, 0)
- self.assertEqual(nums.n_missing_branches, 2)
+ assert nums.n_files == 1
+ assert nums.n_statements == 7
+ assert nums.n_excluded == 1
+ assert nums.n_missing == 3
+ assert nums.n_branches == 2
+ assert nums.n_partial_branches == 0
+ assert nums.n_missing_branches == 2
class TestRunnerPluginTest(CoverageTest):
@@ -1049,13 +1033,13 @@ def pretend_to_be_nose_with_cover(self, erase=False, cd=False):
cov.combine()
cov.save()
cov.report(["no_biggie.py"], show_missing=True)
- self.assertEqual(self.stdout(), textwrap.dedent("""\
+ assert self.stdout() == textwrap.dedent("""\
Name Stmts Miss Cover Missing
--------------------------------------------
no_biggie.py 4 1 75% 4
--------------------------------------------
TOTAL 4 1 75%
- """))
+ """)
if cd:
os.chdir("..")
@@ -1094,13 +1078,13 @@ def pretend_to_be_pytestcov(self, append):
report = StringIO()
cov.report(show_missing=None, ignore_errors=True, file=report, skip_covered=None,
skip_empty=None)
- self.assertEqual(report.getvalue(), textwrap.dedent("""\
+ assert report.getvalue() == textwrap.dedent("""\
Name Stmts Miss Cover
-----------------------------
prog.py 4 1 75%
-----------------------------
TOTAL 4 1 75%
- """))
+ """)
self.assert_file_count(".coverage", 0)
self.assert_file_count(".coverage.*", 1)
@@ -1117,9 +1101,9 @@ def test_config_doesnt_change(self):
self.make_file("simple.py", "a = 1")
cov = coverage.Coverage()
self.start_import_stop(cov, "simple")
- self.assertEqual(cov.get_option("report:show_missing"), False)
+ assert cov.get_option("report:show_missing") is False
cov.report(show_missing=True)
- self.assertEqual(cov.get_option("report:show_missing"), False)
+ assert cov.get_option("report:show_missing") is False
class RelativePathTest(CoverageTest):
@@ -1140,7 +1124,7 @@ def test_moving_stuff(self):
with change_dir("new"):
cov = coverage.Coverage()
cov.load()
- with self.assertRaisesRegex(CoverageException, expected):
+ with pytest.raises(CoverageException, match=expected):
cov.report()
def test_moving_stuff_with_relative(self):
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index f3aa8ebb9..83e9e6b11 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -6,6 +6,7 @@
import pytest
from tests.coveragetest import CoverageTest
+from tests.helpers import assert_count_equal
import coverage
from coverage import env
@@ -94,7 +95,7 @@ def fn(x):
if x % 2: return True
return False
a = fn(1)
- assert a == True
+ assert a is True
""",
arcz=".1 14 45 5. .2 2. 23 3.", arcz_missing="23 3.")
@@ -270,13 +271,10 @@ def test_while_1(self):
# With "while 1", the loop knows it's constant.
if env.PYBEHAVIOR.keep_constant_test:
arcz = ".1 12 23 34 45 36 62 57 7."
- arcz_missing = ""
elif env.PYBEHAVIOR.nix_while_true:
arcz = ".1 13 34 45 36 63 57 7."
- arcz_missing = ""
else:
arcz = ".1 12 23 34 45 36 63 57 7."
- arcz_missing = ""
self.check_coverage("""\
a, i = 1, 0
while 1:
@@ -287,7 +285,6 @@ def test_while_1(self):
assert a == 4 and i == 3
""",
arcz=arcz,
- arcz_missing=arcz_missing,
)
def test_while_true(self):
@@ -322,7 +319,7 @@ def method(self):
return 1
""")
out = self.run_command("coverage run --branch --source=. main.py")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
if env.PYBEHAVIOR.keep_constant_test:
num_stmts = 3
elif env.PYBEHAVIOR.nix_while_true:
@@ -332,7 +329,7 @@ def method(self):
expected = "zero.py {n} {n} 0 0 0% 1-3".format(n=num_stmts)
report = self.report_from_command("coverage report -m")
squeezed = self.squeezed_lines(report)
- self.assertIn(expected, squeezed[3])
+ assert expected in squeezed[3]
def test_bug_496_continue_in_constant_while(self):
# https://github.com/nedbat/coveragepy/issues/496
@@ -1086,11 +1083,12 @@ def double_inputs():
".2 23 34 45 52 2.",
arcz_missing="2.",
)
- self.assertEqual(self.stdout(), "20\n12\n")
+ assert self.stdout() == "20\n12\n"
+ @pytest.mark.skipif(not env.PYBEHAVIOR.yield_from,
+ reason="Python before 3.3 doesn't have 'yield from'"
+ )
def test_yield_from(self):
- if not env.PYBEHAVIOR.yield_from:
- self.skipTest("Python before 3.3 doesn't have 'yield from'")
self.check_coverage("""\
def gen(inp):
i = 2
@@ -1335,9 +1333,10 @@ def test_dict_literal(self):
arcz=".1 19 9.",
)
+ @pytest.mark.skipif(not env.PYBEHAVIOR.unpackings_pep448,
+ reason="Don't have unpacked literals until 3.5"
+ )
def test_unpacked_literals(self):
- if not env.PYBEHAVIOR.unpackings_pep448:
- self.skipTest("Don't have unpacked literals until 3.5")
self.check_coverage("""\
d = {
'a': 2,
@@ -1374,7 +1373,8 @@ def test_pathologically_long_code_object(self):
# opcodes.
# Note that we no longer interpret bytecode at all, but it couldn't
# hurt to keep the test...
- for n in [10, 50, 100, 500, 1000, 2000]:
+ sizes = [10, 50, 100, 500, 1000, 2000]
+ for n in sizes:
code = """\
data = [
""" + "".join("""\
@@ -1387,7 +1387,7 @@ def test_pathologically_long_code_object(self):
print(len(data))
"""
self.check_coverage(code, arcs=[(-1, 1), (1, 2*n+4), (2*n+4, -1)])
- self.assertEqual(self.stdout().split()[-1], str(n))
+ assert self.stdout().split() == [str(n) for n in sizes]
def test_partial_generators(self):
# https://github.com/nedbat/coveragepy/issues/475
@@ -1410,14 +1410,10 @@ def f(a, b):
filename = self.last_module_name + ".py"
fr = cov._get_file_reporter(filename)
arcs_executed = cov._analyze(filename).arcs_executed()
- self.assertEqual(
- fr.missing_arc_description(3, -3, arcs_executed),
- "line 3 didn't finish the generator expression on line 3"
- )
- self.assertEqual(
- fr.missing_arc_description(4, -4, arcs_executed),
- "line 4 didn't run the generator expression on line 4"
- )
+ expected = "line 3 didn't finish the generator expression on line 3"
+ assert expected == fr.missing_arc_description(3, -3, arcs_executed)
+ expected = "line 4 didn't run the generator expression on line 4"
+ assert expected == fr.missing_arc_description(4, -4, arcs_executed)
class DecoratorArcTest(CoverageTest):
@@ -1587,14 +1583,10 @@ def test_lambda_in_dict(self):
)
+@pytest.mark.skipif(not env.PYBEHAVIOR.async_syntax, reason="Async features are new in Python 3.5")
class AsyncTest(CoverageTest):
"""Tests of the new async and await keywords in Python 3.5"""
- def setUp(self):
- if not env.PYBEHAVIOR.async_syntax:
- self.skipTest("Async features are new in Python 3.5")
- super(AsyncTest, self).setUp()
-
def test_async(self):
self.check_coverage("""\
import asyncio
@@ -1620,7 +1612,7 @@ async def print_sum(x, y): # 8
"-89 9C C-8",
arcz_unpredicted="5-3 9-8",
)
- self.assertEqual(self.stdout(), "Compute 1 + 2 ...\n1 + 2 = 3\n")
+ assert self.stdout() == "Compute 1 + 2 ...\n1 + 2 = 3\n"
def test_async_for(self):
self.check_coverage("""\
@@ -1657,7 +1649,7 @@ async def doit(): # G
"-AB BC C-A DE E-A ", # __anext__
arcz_unpredicted="CD",
)
- self.assertEqual(self.stdout(), "a\nb\nc\n.\n")
+ assert self.stdout() == "a\nb\nc\n.\n"
def test_async_with(self):
self.check_coverage("""\
@@ -1739,4 +1731,4 @@ def fun1(x):
data = cov.get_data()
fun1_lines = data.lines(abs_file("fun1.py"))
- self.assertCountEqual(fun1_lines, [1, 2, 5])
+ assert_count_equal(fun1_lines, [1, 2, 5])
diff --git a/tests/test_backward.py b/tests/test_backward.py
index 8acb8707c..d750022b3 100644
--- a/tests/test_backward.py
+++ b/tests/test_backward.py
@@ -3,20 +3,22 @@
"""Tests that our version shims in backward.py are working."""
-from coverage.backunittest import TestCase
+import unittest
+
from coverage.backward import iitems, binary_bytes, bytes_to_ints
+from tests.helpers import assert_count_equal
-class BackwardTest(TestCase):
+class BackwardTest(unittest.TestCase):
"""Tests of things from backward.py."""
def test_iitems(self):
d = {'a': 1, 'b': 2, 'c': 3}
items = [('a', 1), ('b', 2), ('c', 3)]
- self.assertCountEqual(list(iitems(d)), items)
+ assert_count_equal(list(iitems(d)), items)
def test_binary_bytes(self):
byte_values = [0, 255, 17, 23, 42, 57]
bb = binary_bytes(byte_values)
- self.assertEqual(len(bb), len(byte_values))
- self.assertEqual(byte_values, list(bytes_to_ints(bb)))
+ assert len(bb) == len(byte_values)
+ assert byte_values == list(bytes_to_ints(bb))
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 374adb0de..d51410280 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -113,7 +113,7 @@ def mock_command_line(self, args, options=None):
def cmd_executes(self, args, code, ret=OK, options=None):
"""Assert that the `args` end up executing the sequence in `code`."""
called, status = self.mock_command_line(args, options=options)
- self.assertEqual(status, ret, "Wrong status: got %r, wanted %r" % (status, ret))
+ assert status == ret, "Wrong status: got %r, wanted %r" % (status, ret)
# Remove all indentation, and execute with mock globals
code = textwrap.dedent(code)
@@ -136,7 +136,7 @@ def cmd_executes_same(self, args1, args2):
"""Assert that the `args1` executes the same as `args2`."""
m1, r1 = self.mock_command_line(args1)
m2, r2 = self.mock_command_line(args2)
- self.assertEqual(r1, r2)
+ assert r1 == r2
self.assert_same_mock_calls(m1, m2)
def assert_same_mock_calls(self, m1, m2):
@@ -147,7 +147,7 @@ def assert_same_mock_calls(self, m1, m2):
if m1.mock_calls != m2.mock_calls:
pp1 = pprint.pformat(m1.mock_calls)
pp2 = pprint.pformat(m2.mock_calls)
- self.assertMultiLineEqual(pp1+'\n', pp2+'\n')
+ assert pp1+'\n' == pp2+'\n'
def cmd_help(self, args, help_msg=None, topic=None, ret=ERR):
"""Run a command line, and check that it prints the right help.
@@ -157,11 +157,11 @@ def cmd_help(self, args, help_msg=None, topic=None, ret=ERR):
"""
mk, status = self.mock_command_line(args)
- self.assertEqual(status, ret, "Wrong status: got %s, wanted %s" % (status, ret))
+ assert status == ret, "Wrong status: got %s, wanted %s" % (status, ret)
if help_msg:
- self.assertEqual(mk.mock_calls[-1], ('show_help', (help_msg,), {}))
+ assert mk.mock_calls[-1] == ('show_help', (help_msg,), {})
else:
- self.assertEqual(mk.mock_calls[-1], ('show_help', (), {'topic': topic}))
+ assert mk.mock_calls[-1] == ('show_help', (), {'topic': topic})
class BaseCmdLineTestTest(BaseCmdLineTest):
@@ -169,7 +169,7 @@ class BaseCmdLineTestTest(BaseCmdLineTest):
def test_cmd_executes_same(self):
# All the other tests here use self.cmd_executes_same in successful
# ways, so here we just check that it fails.
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.cmd_executes_same("run", "debug")
@@ -218,20 +218,20 @@ def test_combine(self):
# coverage combine with args
self.cmd_executes("combine datadir1", """\
cov = Coverage()
- cov.combine(["datadir1"], strict=True)
+ cov.combine(["datadir1"], strict=True, keep=False)
cov.save()
""")
# coverage combine, appending
self.cmd_executes("combine --append datadir1", """\
cov = Coverage()
cov.load()
- cov.combine(["datadir1"], strict=True)
+ cov.combine(["datadir1"], strict=True, keep=False)
cov.save()
""")
# coverage combine without args
self.cmd_executes("combine", """\
cov = Coverage()
- cov.combine(None, strict=True)
+ cov.combine(None, strict=True, keep=False)
cov.save()
""")
@@ -239,12 +239,12 @@ def test_combine_doesnt_confuse_options_with_args(self):
# https://github.com/nedbat/coveragepy/issues/385
self.cmd_executes("combine --rcfile cov.ini", """\
cov = Coverage(config_file='cov.ini')
- cov.combine(None, strict=True)
+ cov.combine(None, strict=True, keep=False)
cov.save()
""")
self.cmd_executes("combine --rcfile cov.ini data1 data2/more", """\
cov = Coverage(config_file='cov.ini')
- cov.combine(["data1", "data2/more"], strict=True)
+ cov.combine(["data1", "data2/more"], strict=True, keep=False)
cov.save()
""")
@@ -255,15 +255,15 @@ def test_debug(self):
def test_debug_sys(self):
self.command_line("debug sys")
out = self.stdout()
- self.assertIn("version:", out)
- self.assertIn("data_file:", out)
+ assert "version:" in out
+ assert "data_file:" in out
def test_debug_config(self):
self.command_line("debug config")
out = self.stdout()
- self.assertIn("cover_pylib:", out)
- self.assertIn("skip_covered:", out)
- self.assertIn("skip_empty:", out)
+ assert "cover_pylib:" in out
+ assert "skip_covered:" in out
+ assert "skip_empty:" in out
def test_erase(self):
# coverage erase
@@ -529,7 +529,7 @@ def test_run(self):
def test_bad_concurrency(self):
self.command_line("run --concurrency=nothing", ret=ERR)
err = self.stderr()
- self.assertIn("option --concurrency: invalid choice: 'nothing'", err)
+ assert "option --concurrency: invalid choice: 'nothing'" in err
def test_no_multiple_concurrency(self):
# You can't use multiple concurrency values on the command line.
@@ -537,21 +537,17 @@ def test_no_multiple_concurrency(self):
# values for this option, but optparse is not that flexible.
self.command_line("run --concurrency=multiprocessing,gevent foo.py", ret=ERR)
err = self.stderr()
- self.assertIn("option --concurrency: invalid choice: 'multiprocessing,gevent'", err)
+ assert "option --concurrency: invalid choice: 'multiprocessing,gevent'" in err
def test_multiprocessing_needs_config_file(self):
# You can't use command-line args to add options to multiprocessing
# runs, since they won't make it to the subprocesses. You need to use a
# config file.
self.command_line("run --concurrency=multiprocessing --branch foo.py", ret=ERR)
- self.assertIn(
- "Options affecting multiprocessing must only be specified in a configuration file.",
- self.stderr()
- )
- self.assertIn(
- "Remove --branch from the command line.",
- self.stderr()
- )
+ msg = "Options affecting multiprocessing must only be specified in a configuration file."
+ _, err = self.stdouterr()
+ assert msg in err
+ assert "Remove --branch from the command line." in err
def test_run_debug(self):
self.cmd_executes("run --debug=opt1 foo.py", """\
@@ -605,7 +601,7 @@ def test_run_module(self):
def test_run_nothing(self):
self.command_line("run", ret=ERR)
- self.assertIn("Nothing to do", self.stderr())
+ assert "Nothing to do" in self.stderr()
def test_run_from_config(self):
options = {"run:command_line": "myprog.py a 123 'a quoted thing' xyz"}
@@ -660,7 +656,7 @@ def test_run_dashm_only(self):
def test_cant_append_parallel(self):
self.command_line("run --append --parallel-mode foo.py", ret=ERR)
- self.assertIn("Can't append to data files in parallel mode.", self.stderr())
+ assert "Can't append to data files in parallel mode." in self.stderr()
def test_xml(self):
# coverage xml [-i] [--omit DIR,...] [FILE1 FILE2 ...]
@@ -781,7 +777,7 @@ def test_debug_data(self):
data.write()
self.command_line("debug data")
- self.assertMultiLineEqual(self.stdout(), textwrap.dedent("""\
+ assert self.stdout() == textwrap.dedent("""\
-- data ------------------------------------------------------
path: FILENAME
has_arcs: False
@@ -789,16 +785,16 @@ def test_debug_data(self):
2 files:
file1.py: 17 lines [a_plugin]
file2.py: 23 lines
- """).replace("FILENAME", data.data_filename()))
+ """).replace("FILENAME", data.data_filename())
def test_debug_data_with_no_data(self):
data = CoverageData()
self.command_line("debug data")
- self.assertMultiLineEqual(self.stdout(), textwrap.dedent("""\
+ assert self.stdout() == textwrap.dedent("""\
-- data ------------------------------------------------------
path: FILENAME
No data collected
- """).replace("FILENAME", data.data_filename()))
+ """).replace("FILENAME", data.data_filename())
class CmdLineStdoutTest(BaseCmdLineTest):
@@ -807,31 +803,31 @@ class CmdLineStdoutTest(BaseCmdLineTest):
def test_minimum_help(self):
self.command_line("")
out = self.stdout()
- self.assertIn("Code coverage for Python", out)
- self.assertLess(out.count("\n"), 4)
+ assert "Code coverage for Python" in out
+ assert out.count("\n") < 4
def test_version(self):
self.command_line("--version")
out = self.stdout()
- self.assertIn("ersion ", out)
+ assert "ersion " in out
if env.C_TRACER:
- self.assertIn("with C extension", out)
+ assert "with C extension" in out
else:
- self.assertIn("without C extension", out)
- self.assertLess(out.count("\n"), 4)
+ assert "without C extension" in out
+ assert out.count("\n") < 4
+ @pytest.mark.skipif(env.JYTHON, reason="Jython gets mad if you patch sys.argv")
def test_help_contains_command_name(self):
# Command name should be present in help output.
- if env.JYTHON:
- self.skipTest("Jython gets mad if you patch sys.argv")
fake_command_path = "lorem/ipsum/dolor".replace("/", os.sep)
expected_command_name = "dolor"
fake_argv = [fake_command_path, "sit", "amet"]
with mock.patch.object(sys, 'argv', new=fake_argv):
self.command_line("help")
out = self.stdout()
- self.assertIn(expected_command_name, out)
+ assert expected_command_name in out
+ @pytest.mark.skipif(env.JYTHON, reason="Jython gets mad if you patch sys.argv")
def test_help_contains_command_name_from_package(self):
# Command package name should be present in help output.
#
@@ -839,46 +835,44 @@ def test_help_contains_command_name_from_package(self):
# has the `__main__.py` file's patch as the command name. Instead, the command name should
# be derived from the package name.
- if env.JYTHON:
- self.skipTest("Jython gets mad if you patch sys.argv")
fake_command_path = "lorem/ipsum/dolor/__main__.py".replace("/", os.sep)
expected_command_name = "dolor"
fake_argv = [fake_command_path, "sit", "amet"]
with mock.patch.object(sys, 'argv', new=fake_argv):
self.command_line("help")
out = self.stdout()
- self.assertIn(expected_command_name, out)
+ assert expected_command_name in out
def test_help(self):
self.command_line("help")
lines = self.stdout().splitlines()
- self.assertGreater(len(lines), 10)
- self.assertEqual(lines[-1], "Full documentation is at {}".format(__url__))
+ assert len(lines) > 10
+ assert lines[-1] == "Full documentation is at {}".format(__url__)
def test_cmd_help(self):
self.command_line("help run")
out = self.stdout()
lines = out.splitlines()
- self.assertIn("", lines[0])
- self.assertIn("--timid", out)
- self.assertGreater(len(lines), 20)
- self.assertEqual(lines[-1], "Full documentation is at {}".format(__url__))
+ assert "" in lines[0]
+ assert "--timid" in out
+ assert len(lines) > 20
+ assert lines[-1] == "Full documentation is at {}".format(__url__)
def test_unknown_topic(self):
# Should probably be an ERR return, but meh.
self.command_line("help foobar")
lines = self.stdout().splitlines()
- self.assertEqual(lines[0], "Don't know topic 'foobar'")
- self.assertEqual(lines[-1], "Full documentation is at {}".format(__url__))
+ assert lines[0] == "Don't know topic 'foobar'"
+ assert lines[-1] == "Full documentation is at {}".format(__url__)
def test_error(self):
self.command_line("fooey kablooey", ret=ERR)
err = self.stderr()
- self.assertIn("fooey", err)
- self.assertIn("help", err)
+ assert "fooey" in err
+ assert "help" in err
def test_doc_url(self):
- self.assertTrue(__url__.startswith("https://coverage.readthedocs.io"))
+ assert __url__.startswith("https://coverage.readthedocs.io")
class CmdMainTest(CoverageTest):
@@ -914,25 +908,26 @@ def setUp(self):
def test_normal(self):
ret = coverage.cmdline.main(['hello'])
- self.assertEqual(ret, 0)
- self.assertEqual(self.stdout(), "Hello, world!\n")
+ assert ret == 0
+ assert self.stdout() == "Hello, world!\n"
def test_raise(self):
ret = coverage.cmdline.main(['raise'])
- self.assertEqual(ret, 1)
- self.assertEqual(self.stdout(), "")
- err = self.stderr().split('\n')
- self.assertEqual(err[0], 'Traceback (most recent call last):')
- self.assertEqual(err[-3], ' raise Exception("oh noes!")')
- self.assertEqual(err[-2], 'Exception: oh noes!')
+ assert ret == 1
+ out, err = self.stdouterr()
+ assert out == ""
+ err = err.split('\n')
+ assert err[0] == 'Traceback (most recent call last):'
+ assert err[-3] == ' raise Exception("oh noes!")'
+ assert err[-2] == 'Exception: oh noes!'
def test_internalraise(self):
- with self.assertRaisesRegex(ValueError, "coverage is broken"):
+ with pytest.raises(ValueError, match="coverage is broken"):
coverage.cmdline.main(['internalraise'])
def test_exit(self):
ret = coverage.cmdline.main(['exit'])
- self.assertEqual(ret, 23)
+ assert ret == 23
class CoverageReportingFake(object):
diff --git a/tests/test_collector.py b/tests/test_collector.py
index f7e8a4c45..53d7f1752 100644
--- a/tests/test_collector.py
+++ b/tests/test_collector.py
@@ -46,5 +46,5 @@ def otherfunc(x):
# Double-check that our files were checked.
abs_files = {os.path.abspath(f) for f in should_trace_hook.filenames}
- self.assertIn(os.path.abspath("f1.py"), abs_files)
- self.assertIn(os.path.abspath("f2.py"), abs_files)
+ assert os.path.abspath("f1.py") in abs_files
+ assert os.path.abspath("f2.py") in abs_files
diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py
index 2469e2968..86c69cf50 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -6,11 +6,13 @@
import glob
import os
import random
+import re
import sys
import threading
import time
from flaky import flaky
+import pytest
import coverage
from coverage import env
@@ -91,7 +93,7 @@ def test_line_count(self):
print("done")
"""
- self.assertEqual(line_count(CODE), 5)
+ assert line_count(CODE) == 5
# The code common to all the concurrency models.
@@ -227,14 +229,14 @@ def try_some_code(self, code, concurrency, the_module, expected_out=None):
expected_cant_trace = cant_trace_msg(concurrency, the_module)
if expected_cant_trace is not None:
- self.assertEqual(out, expected_cant_trace)
+ assert out == expected_cant_trace
else:
# We can fully measure the code if we are using the C tracer, which
# can support all the concurrency, or if we are using threads.
if expected_out is None:
expected_out = "%d\n" % (sum(range(self.QLIMIT)))
print(code)
- self.assertEqual(out, expected_out)
+ assert out == expected_out
# Read the coverage file and see that try_it.py has all its lines
# executed.
@@ -248,7 +250,7 @@ def try_some_code(self, code, concurrency, the_module, expected_out=None):
print_simple_annotation(code, linenos)
lines = line_count(code)
- self.assertEqual(line_counts(data)['try_it.py'], lines)
+ assert line_counts(data)['try_it.py'] == lines
def test_threads(self):
code = (THREAD + SUM_RANGE_Q + PRINT_SUM_RANGE).format(QLIMIT=self.QLIMIT)
@@ -362,15 +364,11 @@ def process_worker_main(args):
"""
+@pytest.mark.skipif(not multiprocessing, reason="No multiprocessing in this Python")
@flaky(max_runs=30) # Sometimes a test fails due to inherent randomness. Try more times.
class MultiprocessingTest(CoverageTest):
"""Test support of the multiprocessing module."""
- def setUp(self):
- if not multiprocessing:
- self.skipTest("No multiprocessing in this Python") # pragma: only jython
- super(MultiprocessingTest, self).setUp()
-
def try_multiprocessing_code(
self, code, expected_out, the_module, nprocs, concurrency="multiprocessing", args=""
):
@@ -399,17 +397,17 @@ def try_multiprocessing_code(
expected_cant_trace = cant_trace_msg(concurrency, the_module)
if expected_cant_trace is not None:
- self.assertEqual(out, expected_cant_trace)
+ assert out == expected_cant_trace
else:
- self.assertEqual(out.rstrip(), expected_out)
- self.assertEqual(len(glob.glob(".coverage.*")), nprocs + 1)
+ assert out.rstrip() == expected_out
+ assert len(glob.glob(".coverage.*")) == nprocs + 1
out = self.run_command("coverage combine")
- self.assertEqual(out, "")
+ assert out == ""
out = self.run_command("coverage report -m")
last_line = self.squeezed_lines(out)[-1]
- self.assertRegex(last_line, r"TOTAL \d+ 0 100%")
+ assert re.search(r"TOTAL \d+ 0 100%", last_line)
def test_multiprocessing_simple(self):
nprocs = 3
@@ -459,14 +457,14 @@ def try_multiprocessing_code_with_branching(self, code, expected_out):
continue
out = self.run_command("coverage run --rcfile=multi.rc multi.py %s" % (start_method,))
- self.assertEqual(out.rstrip(), expected_out)
+ assert out.rstrip() == expected_out
out = self.run_command("coverage combine")
- self.assertEqual(out, "")
+ assert out == ""
out = self.run_command("coverage report -m")
last_line = self.squeezed_lines(out)[-1]
- self.assertRegex(last_line, r"TOTAL \d+ 0 \d+ 0 100%")
+ assert re.search(r"TOTAL \d+ 0 \d+ 0 100%", last_line)
def test_multiprocessing_with_branching(self):
nprocs = 3
@@ -490,8 +488,8 @@ def test_multiprocessing_bootstrap_error_handling(self):
_crash = _bootstrap
""")
out = self.run_command("coverage run multi.py")
- self.assertIn("Exception during multiprocessing bootstrap init", out)
- self.assertIn("Exception: Crashing because called by _bootstrap", out)
+ assert "Exception during multiprocessing bootstrap init" in out
+ assert "Exception: Crashing because called by _bootstrap" in out
def test_bug890(self):
# chdir in multiprocessing shouldn't keep us from finding the
@@ -510,7 +508,7 @@ def test_bug890(self):
concurrency = multiprocessing
""")
out = self.run_command("coverage run multi.py")
- self.assertEqual(out.splitlines()[-1], "ok")
+ assert out.splitlines()[-1] == "ok"
def test_coverage_stop_in_threads():
diff --git a/tests/test_config.py b/tests/test_config.py
index 4225540c0..b1611c1b8 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -7,8 +7,10 @@
from collections import OrderedDict
import mock
+import pytest
import coverage
+from coverage.config import HandyConfigParser
from coverage.misc import CoverageException
from tests.coveragetest import CoverageTest, UsingModulesMixin
@@ -21,17 +23,17 @@ class ConfigTest(CoverageTest):
def test_default_config(self):
# Just constructing a coverage() object gets the right defaults.
cov = coverage.Coverage()
- self.assertFalse(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, ".coverage")
+ assert not cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == ".coverage"
def test_arguments(self):
# Arguments to the constructor are applied to the configuration.
cov = coverage.Coverage(timid=True, data_file="fooey.dat", concurrency="multiprocessing")
- self.assertTrue(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, "fooey.dat")
- self.assertEqual(cov.config.concurrency, ["multiprocessing"])
+ assert cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == "fooey.dat"
+ assert cov.config.concurrency == ["multiprocessing"]
def test_config_file(self):
# A .coveragerc file will be read into the configuration.
@@ -42,9 +44,9 @@ def test_config_file(self):
data_file = .hello_kitty.data
""")
cov = coverage.Coverage()
- self.assertTrue(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, ".hello_kitty.data")
+ assert cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == ".hello_kitty.data"
def test_named_config_file(self):
# You can name the config file what you like.
@@ -55,9 +57,9 @@ def test_named_config_file(self):
data_file = delete.me
""")
cov = coverage.Coverage(config_file="my_cov.ini")
- self.assertTrue(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, "delete.me")
+ assert cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == "delete.me"
def test_toml_config_file(self):
# A .coveragerc file will be read into the configuration.
@@ -79,18 +81,15 @@ def test_toml_config_file(self):
hello = "world"
""")
cov = coverage.Coverage(config_file="pyproject.toml")
- self.assertTrue(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.concurrency, [u"a", u"b"])
- self.assertEqual(cov.config.data_file, u".hello_kitty.data")
- self.assertEqual(cov.config.plugins, [u"plugins.a_plugin"])
- self.assertEqual(cov.config.precision, 3)
- self.assertEqual(cov.config.html_title, u"tabblo & «ταБЬℓσ»")
- self.assertAlmostEqual(cov.config.fail_under, 90.5)
- self.assertEqual(
- cov.config.get_plugin_options("plugins.a_plugin"),
- {u"hello": u"world"}
- )
+ assert cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.concurrency == [u"a", u"b"]
+ assert cov.config.data_file == u".hello_kitty.data"
+ assert cov.config.plugins == [u"plugins.a_plugin"]
+ assert cov.config.precision == 3
+ assert cov.config.html_title == u"tabblo & «ταБЬℓσ»"
+ assert round(abs(cov.config.fail_under-90.5), 7) == 0
+ assert cov.config.get_plugin_options("plugins.a_plugin") == {u"hello": u"world"}
# Test that our class doesn't reject integers when loading floats
self.make_file("pyproject.toml", """\
@@ -99,8 +98,8 @@ def test_toml_config_file(self):
fail_under = 90
""")
cov = coverage.Coverage(config_file="pyproject.toml")
- self.assertAlmostEqual(cov.config.fail_under, 90)
- self.assertIsInstance(cov.config.fail_under, float)
+ assert round(abs(cov.config.fail_under-90), 7) == 0
+ assert isinstance(cov.config.fail_under, float)
def test_ignored_config_file(self):
# You can disable reading the .coveragerc file.
@@ -110,9 +109,9 @@ def test_ignored_config_file(self):
data_file = delete.me
""")
cov = coverage.Coverage(config_file=False)
- self.assertFalse(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, ".coverage")
+ assert not cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == ".coverage"
def test_config_file_then_args(self):
# The arguments override the .coveragerc file.
@@ -122,9 +121,9 @@ def test_config_file_then_args(self):
data_file = weirdo.file
""")
cov = coverage.Coverage(timid=False, data_file=".mycov")
- self.assertFalse(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, ".mycov")
+ assert not cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == ".mycov"
def test_data_file_from_environment(self):
# There's an environment variable for the data_file.
@@ -135,10 +134,10 @@ def test_data_file_from_environment(self):
""")
self.set_environ("COVERAGE_FILE", "fromenv.dat")
cov = coverage.Coverage()
- self.assertEqual(cov.config.data_file, "fromenv.dat")
+ assert cov.config.data_file == "fromenv.dat"
# But the constructor arguments override the environment variable.
cov = coverage.Coverage(data_file="fromarg.dat")
- self.assertEqual(cov.config.data_file, "fromarg.dat")
+ assert cov.config.data_file == "fromarg.dat"
def test_debug_from_environment(self):
self.make_file(".coveragerc", """\
@@ -147,7 +146,7 @@ def test_debug_from_environment(self):
""")
self.set_environ("COVERAGE_DEBUG", "callers, fooey")
cov = coverage.Coverage()
- self.assertEqual(cov.config.debug, ["dataio", "pids", "callers", "fooey"])
+ assert cov.config.debug == ["dataio", "pids", "callers", "fooey"]
def test_rcfile_from_environment(self):
self.make_file("here.ini", """\
@@ -156,12 +155,12 @@ def test_rcfile_from_environment(self):
""")
self.set_environ("COVERAGE_RCFILE", "here.ini")
cov = coverage.Coverage()
- self.assertEqual(cov.config.data_file, "overthere.dat")
+ assert 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):
+ with pytest.raises(CoverageException, match=msg):
coverage.Coverage()
def test_parse_errors(self):
@@ -185,7 +184,7 @@ def test_parse_errors(self):
for bad_config, msg in bad_configs_and_msgs:
print("Trying %r" % bad_config)
self.make_file(".coveragerc", bad_config)
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
coverage.Coverage()
def test_toml_parse_errors(self):
@@ -211,7 +210,7 @@ def test_toml_parse_errors(self):
for bad_config, msg in bad_configs_and_msgs:
print("Trying %r" % bad_config)
self.make_file("pyproject.toml", bad_config)
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
coverage.Coverage()
def test_environment_vars_in_config(self):
@@ -232,12 +231,9 @@ def test_environment_vars_in_config(self):
self.set_environ("THING", "ZZZ")
self.set_environ("OKAY", "yes")
cov = coverage.Coverage()
- self.assertEqual(cov.config.data_file, "hello-world.fooey")
- self.assertEqual(cov.config.branch, True)
- self.assertEqual(
- cov.config.exclude_list,
- ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"]
- )
+ assert cov.config.data_file == "hello-world.fooey"
+ assert cov.config.branch is True
+ assert cov.config.exclude_list == ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"]
def test_environment_vars_in_toml_config(self):
# Config files can have $envvars in them.
@@ -258,12 +254,9 @@ def test_environment_vars_in_toml_config(self):
self.set_environ("DATA_FILE", "hello-world")
self.set_environ("THING", "ZZZ")
cov = coverage.Coverage()
- self.assertEqual(cov.config.data_file, "hello-world.fooey")
- self.assertEqual(cov.config.branch, True)
- self.assertEqual(
- cov.config.exclude_list,
- ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"]
- )
+ assert cov.config.data_file == "hello-world.fooey"
+ assert cov.config.branch is True
+ assert cov.config.exclude_list == ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"]
def test_tilde_in_config(self):
# Config entries that are file paths can be tilde-expanded.
@@ -296,11 +289,11 @@ def expanduser(s):
with mock.patch.object(coverage.config.os.path, 'expanduser', new=expanduser):
cov = coverage.Coverage()
- self.assertEqual(cov.config.data_file, "/Users/me/data.file")
- self.assertEqual(cov.config.html_dir, "/Users/joe/html_dir")
- self.assertEqual(cov.config.xml_output, "/Users/me/somewhere/xml.out")
- self.assertEqual(cov.config.exclude_list, ["~/data.file", "~joe/html_dir"])
- self.assertEqual(cov.config.paths, {'mapping': ['/Users/me/src', '/Users/joe/source']})
+ assert cov.config.data_file == "/Users/me/data.file"
+ assert cov.config.html_dir == "/Users/joe/html_dir"
+ assert cov.config.xml_output == "/Users/me/somewhere/xml.out"
+ assert cov.config.exclude_list == ["~/data.file", "~joe/html_dir"]
+ assert cov.config.paths == {'mapping': ['/Users/me/src', '/Users/joe/source']}
def test_tilde_in_toml_config(self):
# Config entries that are file paths can be tilde-expanded.
@@ -329,23 +322,23 @@ def expanduser(s):
with mock.patch.object(coverage.config.os.path, 'expanduser', new=expanduser):
cov = coverage.Coverage()
- self.assertEqual(cov.config.data_file, "/Users/me/data.file")
- self.assertEqual(cov.config.html_dir, "/Users/joe/html_dir")
- self.assertEqual(cov.config.xml_output, "/Users/me/somewhere/xml.out")
- self.assertEqual(cov.config.exclude_list, ["~/data.file", "~joe/html_dir"])
+ assert cov.config.data_file == "/Users/me/data.file"
+ assert cov.config.html_dir == "/Users/joe/html_dir"
+ assert cov.config.xml_output == "/Users/me/somewhere/xml.out"
+ assert cov.config.exclude_list == ["~/data.file", "~joe/html_dir"]
def test_tweaks_after_constructor(self):
# set_option can be used after construction to affect the config.
cov = coverage.Coverage(timid=True, data_file="fooey.dat")
cov.set_option("run:timid", False)
- self.assertFalse(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, "fooey.dat")
+ assert not cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == "fooey.dat"
- self.assertFalse(cov.get_option("run:timid"))
- self.assertFalse(cov.get_option("run:branch"))
- self.assertEqual(cov.get_option("run:data_file"), "fooey.dat")
+ assert not cov.get_option("run:timid")
+ assert not cov.get_option("run:branch")
+ assert cov.get_option("run:data_file") == "fooey.dat"
def test_tweaks_paths_after_constructor(self):
self.make_file(".coveragerc", """\
@@ -363,24 +356,24 @@ def test_tweaks_paths_after_constructor(self):
old_paths["second"] = ["/second/a", "/second/b"]
cov = coverage.Coverage()
paths = cov.get_option("paths")
- self.assertEqual(paths, old_paths)
+ assert paths == old_paths
new_paths = OrderedDict()
new_paths['magic'] = ['src', 'ok']
cov.set_option("paths", new_paths)
- self.assertEqual(cov.get_option("paths"), new_paths)
+ assert cov.get_option("paths") == new_paths
def test_tweak_error_checking(self):
# Trying to set an unknown config value raises an error.
cov = coverage.Coverage()
- with self.assertRaisesRegex(CoverageException, "No such option: 'run:xyzzy'"):
+ with pytest.raises(CoverageException, match="No such option: 'run:xyzzy'"):
cov.set_option("run:xyzzy", 12)
- with self.assertRaisesRegex(CoverageException, "No such option: 'xyzzy:foo'"):
+ with pytest.raises(CoverageException, match="No such option: 'xyzzy:foo'"):
cov.set_option("xyzzy:foo", 12)
- with self.assertRaisesRegex(CoverageException, "No such option: 'run:xyzzy'"):
+ with pytest.raises(CoverageException, match="No such option: 'run:xyzzy'"):
_ = cov.get_option("run:xyzzy")
- with self.assertRaisesRegex(CoverageException, "No such option: 'xyzzy:foo'"):
+ with pytest.raises(CoverageException, match="No such option: 'xyzzy:foo'"):
_ = cov.get_option("xyzzy:foo")
def test_tweak_plugin_options(self):
@@ -389,12 +382,12 @@ def test_tweak_plugin_options(self):
cov.set_option("run:plugins", ["fooey.plugin", "xyzzy.coverage.plugin"])
cov.set_option("fooey.plugin:xyzzy", 17)
cov.set_option("xyzzy.coverage.plugin:plugh", ["a", "b"])
- with self.assertRaisesRegex(CoverageException, "No such option: 'no_such.plugin:foo'"):
+ with pytest.raises(CoverageException, match="No such option: 'no_such.plugin:foo'"):
cov.set_option("no_such.plugin:foo", 23)
- self.assertEqual(cov.get_option("fooey.plugin:xyzzy"), 17)
- self.assertEqual(cov.get_option("xyzzy.coverage.plugin:plugh"), ["a", "b"])
- with self.assertRaisesRegex(CoverageException, "No such option: 'no_such.plugin:foo'"):
+ assert cov.get_option("fooey.plugin:xyzzy") == 17
+ assert cov.get_option("xyzzy.coverage.plugin:plugh") == ["a", "b"]
+ with pytest.raises(CoverageException, match="No such option: 'no_such.plugin:foo'"):
_ = cov.get_option("no_such.plugin:foo")
def test_unknown_option(self):
@@ -403,7 +396,7 @@ def test_unknown_option(self):
xyzzy = 17
""")
msg = r"Unrecognized option '\[run\] xyzzy=' in config file .coveragerc"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
_ = coverage.Coverage()
def test_unknown_option_toml(self):
@@ -412,7 +405,7 @@ def test_unknown_option_toml(self):
xyzzy = 17
""")
msg = r"Unrecognized option '\[tool.coverage.run\] xyzzy=' in config file pyproject.toml"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
_ = coverage.Coverage()
def test_misplaced_option(self):
@@ -421,7 +414,7 @@ def test_misplaced_option(self):
branch = True
""")
msg = r"Unrecognized option '\[report\] branch=' in config file .coveragerc"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
_ = coverage.Coverage()
def test_unknown_option_in_other_ini_file(self):
@@ -430,19 +423,19 @@ def test_unknown_option_in_other_ini_file(self):
huh = what?
""")
msg = (r"Unrecognized option '\[coverage:run\] huh=' in config file setup.cfg")
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
_ = coverage.Coverage()
- def test_note_is_obsolete(self):
- self.make_file("main.py", "a = 1")
- self.make_file(".coveragerc", """\
+ def test_exceptions_from_missing_things(self):
+ self.make_file("config.ini", """\
[run]
- note = I am here I am here I am here!
+ branch = True
""")
- cov = coverage.Coverage()
- with self.assert_warnings(cov, [r"The '\[run] note' setting is no longer supported."]):
- self.start_import_stop(cov, "main")
- cov.report()
+ config = HandyConfigParser("config.ini")
+ with pytest.raises(Exception, match="No section: 'xyzzy'"):
+ config.options("xyzzy")
+ with pytest.raises(Exception, match="No option 'foo' in section: 'xyzzy'"):
+ config.get("xyzzy", "foo")
class ConfigFileTest(UsingModulesMixin, CoverageTest):
@@ -546,49 +539,49 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
def assert_config_settings_are_correct(self, cov):
"""Check that `cov` has all the settings from LOTSA_SETTINGS."""
- self.assertTrue(cov.config.timid)
- self.assertEqual(cov.config.data_file, "something_or_other.dat")
- self.assertTrue(cov.config.branch)
- self.assertTrue(cov.config.cover_pylib)
- self.assertEqual(cov.config.debug, ["callers", "pids", "dataio"])
- self.assertTrue(cov.config.parallel)
- self.assertEqual(cov.config.concurrency, ["thread"])
- self.assertEqual(cov.config.source, ["myapp"])
- self.assertEqual(cov.config.source_pkgs, ["ned"])
- self.assertEqual(cov.config.disable_warnings, ["abcd", "efgh"])
-
- self.assertEqual(cov.get_exclude_list(), ["if 0:", r"pragma:?\s+no cover", "another_tab"])
- self.assertTrue(cov.config.ignore_errors)
- self.assertEqual(cov.config.run_omit, ["twenty"])
- self.assertEqual(cov.config.report_omit, ["one", "another", "some_more", "yet_more"])
- self.assertEqual(cov.config.report_include, ["thirty"])
- self.assertEqual(cov.config.precision, 3)
-
- self.assertEqual(cov.config.partial_list, [r"pragma:?\s+no branch"])
- self.assertEqual(cov.config.partial_always_list, ["if 0:", "while True:"])
- self.assertEqual(cov.config.plugins, ["plugins.a_plugin", "plugins.another"])
- self.assertTrue(cov.config.show_missing)
- self.assertTrue(cov.config.skip_covered)
- self.assertTrue(cov.config.skip_empty)
- self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere")
- self.assertEqual(cov.config.extra_css, "something/extra.css")
- self.assertEqual(cov.config.html_title, "Title & nums # nums!")
-
- self.assertEqual(cov.config.xml_output, "mycov.xml")
- self.assertEqual(cov.config.xml_package_depth, 17)
-
- self.assertEqual(cov.config.paths, {
+ assert cov.config.timid
+ assert cov.config.data_file == "something_or_other.dat"
+ assert cov.config.branch
+ assert cov.config.cover_pylib
+ assert cov.config.debug == ["callers", "pids", "dataio"]
+ assert cov.config.parallel
+ assert cov.config.concurrency == ["thread"]
+ assert cov.config.source == ["myapp"]
+ assert cov.config.source_pkgs == ["ned"]
+ assert cov.config.disable_warnings == ["abcd", "efgh"]
+
+ assert cov.get_exclude_list() == ["if 0:", r"pragma:?\s+no cover", "another_tab"]
+ assert cov.config.ignore_errors
+ assert cov.config.run_omit == ["twenty"]
+ assert cov.config.report_omit == ["one", "another", "some_more", "yet_more"]
+ assert cov.config.report_include == ["thirty"]
+ assert cov.config.precision == 3
+
+ assert cov.config.partial_list == [r"pragma:?\s+no branch"]
+ assert cov.config.partial_always_list == ["if 0:", "while True:"]
+ assert cov.config.plugins == ["plugins.a_plugin", "plugins.another"]
+ assert cov.config.show_missing
+ assert cov.config.skip_covered
+ assert cov.config.skip_empty
+ assert cov.config.html_dir == r"c:\tricky\dir.somewhere"
+ assert cov.config.extra_css == "something/extra.css"
+ assert cov.config.html_title == "Title & nums # nums!"
+
+ assert cov.config.xml_output == "mycov.xml"
+ assert cov.config.xml_package_depth == 17
+
+ assert cov.config.paths == {
'source': ['.', '/home/ned/src/'],
'other': ['other', '/home/ned/other', 'c:\\Ned\\etc']
- })
+ }
- self.assertEqual(cov.config.get_plugin_options("plugins.a_plugin"), {
+ assert cov.config.get_plugin_options("plugins.a_plugin") == {
'hello': 'world',
'names': 'Jane/John/Jenny',
- })
- self.assertEqual(cov.config.get_plugin_options("plugins.another"), {})
- self.assertEqual(cov.config.json_show_contexts, True)
- self.assertEqual(cov.config.json_pretty_print, True)
+ }
+ assert cov.config.get_plugin_options("plugins.another") == {}
+ assert cov.config.json_show_contexts is True
+ assert cov.config.json_pretty_print is True
def test_config_file_settings(self):
self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section=""))
@@ -633,9 +626,9 @@ def check_other_not_read_if_coveragerc(self, fname):
branch = true
""")
cov = coverage.Coverage()
- self.assertEqual(cov.config.run_include, ["foo"])
- self.assertEqual(cov.config.run_omit, None)
- self.assertEqual(cov.config.branch, False)
+ assert cov.config.run_include == ["foo"]
+ assert cov.config.run_omit is None
+ assert cov.config.branch is False
def test_setupcfg_only_if_not_coveragerc(self):
self.check_other_not_read_if_coveragerc("setup.cfg")
@@ -651,8 +644,8 @@ def check_other_config_need_prefixes(self, fname):
branch = true
""")
cov = coverage.Coverage()
- self.assertEqual(cov.config.run_omit, None)
- self.assertEqual(cov.config.branch, False)
+ assert cov.config.run_omit is None
+ assert cov.config.branch is False
def test_setupcfg_only_if_prefixed(self):
self.check_other_config_need_prefixes("setup.cfg")
@@ -689,8 +682,8 @@ def test_non_ascii(self):
self.set_environ("TOX_ENVNAME", "weirdo")
cov = coverage.Coverage()
- self.assertEqual(cov.config.exclude_list, ["first", "✘weirdo", "third"])
- self.assertEqual(cov.config.html_title, "tabblo & «ταБЬℓσ» # numbers")
+ assert cov.config.exclude_list == ["first", "✘weirdo", "third"]
+ assert cov.config.html_title == "tabblo & «ταБЬℓσ» # numbers"
def test_unreadable_config(self):
# If a config file is explicitly specified, then it is an error for it
@@ -701,20 +694,31 @@ def test_unreadable_config(self):
]
for bad_file in bad_files:
msg = "Couldn't read %r as a config file" % bad_file
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
coverage.Coverage(config_file=bad_file)
def test_nocoveragerc_file_when_specified(self):
cov = coverage.Coverage(config_file=".coveragerc")
- self.assertFalse(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, ".coverage")
+ assert not cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == ".coverage"
+
+ def test_note_is_obsolete(self):
+ self.make_file("main.py", "a = 1")
+ self.make_file(".coveragerc", """\
+ [run]
+ note = I am here I am here I am here!
+ """)
+ cov = coverage.Coverage()
+ with self.assert_warnings(cov, [r"The '\[run] note' setting is no longer supported."]):
+ self.start_import_stop(cov, "main")
+ cov.report()
def test_no_toml_installed_no_toml(self):
# Can't read a toml file that doesn't exist.
with without_module(coverage.tomlconfig, 'toml'):
msg = "Couldn't read 'cov.toml' as a config file"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
coverage.Coverage(config_file="cov.toml")
def test_no_toml_installed_explicit_toml(self):
@@ -722,7 +726,7 @@ def test_no_toml_installed_explicit_toml(self):
self.make_file("cov.toml", "# A toml file!")
with without_module(coverage.tomlconfig, 'toml'):
msg = "Can't read 'cov.toml' without TOML support"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
coverage.Coverage(config_file="cov.toml")
def test_no_toml_installed_pyproject_toml(self):
@@ -734,7 +738,7 @@ def test_no_toml_installed_pyproject_toml(self):
""")
with without_module(coverage.tomlconfig, 'toml'):
msg = "Can't read 'pyproject.toml' without TOML support"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
coverage.Coverage()
def test_no_toml_installed_pyproject_no_coverage(self):
@@ -747,6 +751,6 @@ def test_no_toml_installed_pyproject_no_coverage(self):
with without_module(coverage.tomlconfig, 'toml'):
cov = coverage.Coverage()
# We get default settings:
- self.assertFalse(cov.config.timid)
- self.assertFalse(cov.config.branch)
- self.assertEqual(cov.config.data_file, ".coverage")
+ assert not cov.config.timid
+ assert not cov.config.branch
+ assert cov.config.data_file == ".coverage"
diff --git a/tests/test_context.py b/tests/test_context.py
index 137300a59..f51befae3 100644
--- a/tests/test_context.py
+++ b/tests/test_context.py
@@ -6,12 +6,15 @@
import inspect
import os.path
+import pytest
+
import coverage
from coverage import env
from coverage.context import qualname_from_frame
from coverage.data import CoverageData
from tests.coveragetest import CoverageTest
+from tests.helpers import assert_count_equal
class StaticContextTest(CoverageTest):
@@ -22,14 +25,14 @@ def test_no_context(self):
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
data = cov.get_data()
- self.assertCountEqual(data.measured_contexts(), [""])
+ assert_count_equal(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"])
+ assert_count_equal(data.measured_contexts(), ["gooey"])
SOURCE = """\
a = 1
@@ -64,10 +67,10 @@ def test_combining_line_contexts(self):
for data in datas:
combined.update(data)
- self.assertEqual(combined.measured_contexts(), {'red', 'blue'})
+ assert 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'])
+ assert_count_equal(full_names, ['red.py', 'blue.py'])
fred = full_names['red.py']
fblue = full_names['blue.py']
@@ -75,7 +78,7 @@ def test_combining_line_contexts(self):
def assert_combined_lines(filename, context, lines):
# pylint: disable=cell-var-from-loop
combined.set_query_context(context)
- self.assertEqual(combined.lines(filename), lines)
+ assert combined.lines(filename) == lines
assert_combined_lines(fred, 'red', self.LINES)
assert_combined_lines(fred, 'blue', [])
@@ -89,10 +92,10 @@ def test_combining_arc_contexts(self):
for data in datas:
combined.update(data)
- self.assertEqual(combined.measured_contexts(), {'red', 'blue'})
+ assert 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'])
+ assert_count_equal(full_names, ['red.py', 'blue.py'])
fred = full_names['red.py']
fblue = full_names['blue.py']
@@ -100,7 +103,7 @@ def test_combining_arc_contexts(self):
def assert_combined_lines(filename, context, lines):
# pylint: disable=cell-var-from-loop
combined.set_query_context(context)
- self.assertEqual(combined.lines(filename), lines)
+ assert combined.lines(filename) == lines
assert_combined_lines(fred, 'red', self.LINES)
assert_combined_lines(fred, 'blue', [])
@@ -110,7 +113,7 @@ def assert_combined_lines(filename, context, lines):
def assert_combined_arcs(filename, context, lines):
# pylint: disable=cell-var-from-loop
combined.set_query_context(context)
- self.assertEqual(combined.arcs(filename), lines)
+ assert combined.arcs(filename) == lines
assert_combined_arcs(fred, 'red', self.ARCS)
assert_combined_arcs(fred, 'blue', [])
@@ -157,13 +160,14 @@ def test_dynamic_alone(self):
full_names = {os.path.basename(f): f for f in data.measured_files()}
fname = full_names["two_tests.py"]
- self.assertCountEqual(
+ assert_count_equal(
data.measured_contexts(),
- ["", "two_tests.test_one", "two_tests.test_two"])
+ ["", "two_tests.test_one", "two_tests.test_two"]
+ )
def assert_context_lines(context, lines):
data.set_query_context(context)
- self.assertCountEqual(lines, data.lines(fname))
+ assert_count_equal(lines, data.lines(fname))
assert_context_lines("", self.OUTER_LINES)
assert_context_lines("two_tests.test_one", self.TEST_ONE_LINES)
@@ -178,13 +182,14 @@ def test_static_and_dynamic(self):
full_names = {os.path.basename(f): f for f in data.measured_files()}
fname = full_names["two_tests.py"]
- self.assertCountEqual(
+ assert_count_equal(
data.measured_contexts(),
- ["stat", "stat|two_tests.test_one", "stat|two_tests.test_two"])
+ ["stat", "stat|two_tests.test_one", "stat|two_tests.test_two"]
+ )
def assert_context_lines(context, lines):
data.set_query_context(context)
- self.assertCountEqual(lines, data.lines(fname))
+ assert_count_equal(lines, data.lines(fname))
assert_context_lines("stat", self.OUTER_LINES)
assert_context_lines("stat|two_tests.test_one", self.TEST_ONE_LINES)
@@ -251,40 +256,37 @@ class QualnameTest(CoverageTest):
run_in_temp_dir = False
def test_method(self):
- self.assertEqual(Parent().meth(), "tests.test_context.Parent.meth")
+ assert Parent().meth() == "tests.test_context.Parent.meth"
def test_inherited_method(self):
- self.assertEqual(Child().meth(), "tests.test_context.Parent.meth")
+ assert Child().meth() == "tests.test_context.Parent.meth"
def test_mi_inherited_method(self):
- self.assertEqual(MultiChild().meth(), "tests.test_context.Parent.meth")
+ assert MultiChild().meth() == "tests.test_context.Parent.meth"
def test_no_arguments(self):
- self.assertEqual(no_arguments(), "tests.test_context.no_arguments")
+ assert no_arguments() == "tests.test_context.no_arguments"
def test_plain_old_function(self):
- self.assertEqual(
- plain_old_function(0, 1), "tests.test_context.plain_old_function")
+ assert plain_old_function(0, 1) == "tests.test_context.plain_old_function"
def test_fake_out(self):
- self.assertEqual(fake_out(0), "tests.test_context.fake_out")
+ assert fake_out(0) == "tests.test_context.fake_out"
def test_property(self):
- self.assertEqual(
- Parent().a_property, "tests.test_context.Parent.a_property")
+ assert Parent().a_property == "tests.test_context.Parent.a_property"
def test_changeling(self):
c = Child()
c.meth = patch_meth
- self.assertEqual(c.meth(c), "tests.test_context.patch_meth")
+ assert c.meth(c) == "tests.test_context.patch_meth"
+ @pytest.mark.skipif(not env.PY2, reason="Old-style classes are only in Python 2")
def test_oldstyle(self):
- if not env.PY2:
- self.skipTest("Old-style classes are only in Python 2")
- self.assertEqual(OldStyle().meth(), "tests.test_context.OldStyle.meth")
- self.assertEqual(OldChild().meth(), "tests.test_context.OldStyle.meth")
+ assert OldStyle().meth() == "tests.test_context.OldStyle.meth"
+ assert OldChild().meth() == "tests.test_context.OldStyle.meth"
def test_bug_829(self):
# A class with a name like a function shouldn't confuse qualname_from_frame.
class test_something(object): # pylint: disable=unused-variable
- self.assertEqual(get_qualname(), None)
+ assert get_qualname() is None
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
index 68eea1150..30a8edc5a 100644
--- a/tests/test_coverage.py
+++ b/tests/test_coverage.py
@@ -4,6 +4,8 @@
"""Tests for coverage.py."""
+import pytest
+
import coverage
from coverage import env
from coverage.misc import CoverageException
@@ -50,7 +52,7 @@ def test_successful_coverage(self):
def test_failed_coverage(self):
# If the lines are wrong, the message shows right and wrong.
- with self.assertRaisesRegex(AssertionError, r"\[1, 2] != \[1]"):
+ with pytest.raises(AssertionError, match=r"\[1, 2] != \[1]"):
self.check_coverage("""\
a = 1
b = 2
@@ -59,7 +61,7 @@ def test_failed_coverage(self):
)
# If the list of lines possibilities is wrong, the msg shows right.
msg = r"None of the lines choices matched \[1, 2]"
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.check_coverage("""\
a = 1
b = 2
@@ -67,7 +69,7 @@ def test_failed_coverage(self):
([1], [2])
)
# If the missing lines are wrong, the message shows right and wrong.
- with self.assertRaisesRegex(AssertionError, r"'3' != '37'"):
+ with pytest.raises(AssertionError, match=r"'3' != '37'"):
self.check_coverage("""\
a = 1
if a == 2:
@@ -78,7 +80,7 @@ def test_failed_coverage(self):
)
# If the missing lines possibilities are wrong, the msg shows right.
msg = r"None of the missing choices matched '3'"
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.check_coverage("""\
a = 1
if a == 2:
@@ -90,14 +92,14 @@ def test_failed_coverage(self):
def test_exceptions_really_fail(self):
# An assert in the checked code will really raise up to us.
- with self.assertRaisesRegex(AssertionError, "This is bad"):
+ with pytest.raises(AssertionError, match="This is bad"):
self.check_coverage("""\
a = 1
assert a == 99, "This is bad"
"""
)
# Other exceptions too.
- with self.assertRaisesRegex(ZeroDivisionError, "division"):
+ with pytest.raises(ZeroDivisionError, match="division"):
self.check_coverage("""\
a = 1
assert a == 1, "This is good"
@@ -341,10 +343,8 @@ def test_del(self):
""",
[1,2,3,6,9], "")
+ @pytest.mark.skipif(env.PY3, reason="No more print statement in Python 3.")
def test_print(self):
- if env.PY3: # Print statement is gone in Py3k.
- self.skipTest("No more print statement in Python 3.")
-
self.check_coverage("""\
print "hello, world!"
print ("hey: %d" %
@@ -484,12 +484,11 @@ def test_continue(self):
""",
lines=lines, missing=missing)
+ @pytest.mark.skipif(env.PY2, reason="Expected failure: peephole optimization of jumps to jumps")
def test_strange_unexecuted_continue(self):
# Peephole optimization of jumps to jumps can mean that some statements
# never hit the line tracer. The behavior is different in different
# versions of Python, so be careful when running this test.
- if env.PY2:
- self.skipTest("Expected failure: peephole optimization of jumps to jumps")
self.check_coverage("""\
a = b = c = 0
for n in range(100):
@@ -733,7 +732,7 @@ def test_elif(self):
z = 7
assert x == 3
""",
- [1,2,3,4,5,7,8], "4-7", report="7 3 4 1 45% 2->4, 4-7",
+ [1,2,3,4,5,7,8], "4-7", report="7 3 4 1 45% 4-7",
)
self.check_coverage("""\
a = 1; b = 2; c = 3;
@@ -745,7 +744,7 @@ def test_elif(self):
z = 7
assert y == 5
""",
- [1,2,3,4,5,7,8], "3, 7", report="7 2 4 2 64% 2->3, 3, 4->7, 7",
+ [1,2,3,4,5,7,8], "3, 7", report="7 2 4 2 64% 3, 7",
)
self.check_coverage("""\
a = 1; b = 2; c = 3;
@@ -757,7 +756,7 @@ def test_elif(self):
z = 7
assert z == 7
""",
- [1,2,3,4,5,7,8], "3, 5", report="7 2 4 2 64% 2->3, 3, 4->5, 5",
+ [1,2,3,4,5,7,8], "3, 5", report="7 2 4 2 64% 3, 5",
)
def test_elif_no_else(self):
@@ -769,7 +768,7 @@ def test_elif_no_else(self):
y = 5
assert x == 3
""",
- [1,2,3,4,5,6], "4-5", report="6 2 4 1 50% 2->4, 4-5",
+ [1,2,3,4,5,6], "4-5", report="6 2 4 1 50% 4-5",
)
self.check_coverage("""\
a = 1; b = 2; c = 3;
@@ -779,7 +778,7 @@ def test_elif_no_else(self):
y = 5
assert y == 5
""",
- [1,2,3,4,5,6], "3", report="6 1 4 2 70% 2->3, 3, 4->6",
+ [1,2,3,4,5,6], "3", report="6 1 4 2 70% 3, 4->6",
)
def test_elif_bizarre(self):
@@ -1844,7 +1843,7 @@ def test_not_singleton(self):
coverage.Coverage()
def test_old_name_and_new_name(self):
- self.assertIs(coverage.coverage, coverage.Coverage)
+ assert coverage.coverage is coverage.Coverage
class ReportingTest(CoverageTest):
@@ -1857,19 +1856,19 @@ class ReportingTest(CoverageTest):
def test_no_data_to_report_on_annotate(self):
# Reporting with no data produces a nice message and no output
# directory.
- with self.assertRaisesRegex(CoverageException, "No data to report."):
+ with pytest.raises(CoverageException, match="No data to report."):
self.command_line("annotate -d ann")
self.assert_doesnt_exist("ann")
def test_no_data_to_report_on_html(self):
# Reporting with no data produces a nice message and no output
# directory.
- with self.assertRaisesRegex(CoverageException, "No data to report."):
+ with pytest.raises(CoverageException, match="No data to report."):
self.command_line("html -d htmlcov")
self.assert_doesnt_exist("htmlcov")
def test_no_data_to_report_on_xml(self):
# Reporting with no data produces a nice message.
- with self.assertRaisesRegex(CoverageException, "No data to report."):
+ with pytest.raises(CoverageException, match="No data to report."):
self.command_line("xml")
self.assert_doesnt_exist("coverage.xml")
diff --git a/tests/test_data.py b/tests/test_data.py
index b3ac718e1..eac9c36fa 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -11,6 +11,7 @@
import threading
import mock
+import pytest
from coverage.data import CoverageData, combine_parallel_data
from coverage.data import add_data_to_hash, line_counts
@@ -19,6 +20,7 @@
from coverage.misc import CoverageException
from tests.coveragetest import CoverageTest
+from tests.helpers import assert_count_equal
LINES_1 = {
@@ -77,28 +79,28 @@ class DataTestHelpers(CoverageTest):
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)
+ assert line_counts(covdata, fullpath) == counts
def assert_measured_files(self, covdata, measured):
"""Check that `covdata`'s measured files are `measured`."""
- self.assertCountEqual(covdata.measured_files(), measured)
+ assert_count_equal(covdata.measured_files(), measured)
def assert_lines1_data(self, covdata):
"""Check that `covdata` has the data from LINES1."""
self.assert_line_counts(covdata, SUMMARY_1)
self.assert_measured_files(covdata, MEASURED_FILES_1)
- self.assertCountEqual(covdata.lines("a.py"), A_PY_LINES_1)
- self.assertFalse(covdata.has_arcs())
+ assert_count_equal(covdata.lines("a.py"), A_PY_LINES_1)
+ assert not covdata.has_arcs()
def assert_arcs3_data(self, covdata):
"""Check that `covdata` has the data from ARCS3."""
self.assert_line_counts(covdata, SUMMARY_3)
self.assert_measured_files(covdata, MEASURED_FILES_3)
- self.assertCountEqual(covdata.lines("x.py"), X_PY_LINES_3)
- self.assertCountEqual(covdata.arcs("x.py"), X_PY_ARCS_3)
- self.assertCountEqual(covdata.lines("y.py"), Y_PY_LINES_3)
- self.assertCountEqual(covdata.arcs("y.py"), Y_PY_ARCS_3)
- self.assertTrue(covdata.has_arcs())
+ assert_count_equal(covdata.lines("x.py"), X_PY_LINES_3)
+ assert_count_equal(covdata.arcs("x.py"), X_PY_ARCS_3)
+ assert_count_equal(covdata.lines("y.py"), Y_PY_LINES_3)
+ assert_count_equal(covdata.arcs("y.py"), Y_PY_ARCS_3)
+ assert covdata.has_arcs()
class CoverageDataTest(DataTestHelpers, CoverageTest):
@@ -108,27 +110,27 @@ class CoverageDataTest(DataTestHelpers, CoverageTest):
def test_empty_data_is_false(self):
covdata = CoverageData()
- self.assertFalse(covdata)
+ assert not covdata
def test_line_data_is_true(self):
covdata = CoverageData()
covdata.add_lines(LINES_1)
- self.assertTrue(covdata)
+ assert covdata
def test_arc_data_is_true(self):
covdata = CoverageData()
covdata.add_arcs(ARCS_3)
- self.assertTrue(covdata)
+ assert covdata
def test_empty_line_data_is_false(self):
covdata = CoverageData()
covdata.add_lines({})
- self.assertFalse(covdata)
+ assert not covdata
def test_empty_arc_data_is_false(self):
covdata = CoverageData()
covdata.add_arcs({})
- self.assertFalse(covdata)
+ assert not covdata
def test_adding_lines(self):
covdata = CoverageData()
@@ -157,13 +159,15 @@ def test_ok_to_add_arcs_twice(self):
def test_cant_add_arcs_with_lines(self):
covdata = CoverageData()
covdata.add_lines(LINES_1)
- with self.assertRaisesRegex(CoverageException, "Can't add arcs to existing line data"):
+ msg = "Can't add branch measurements to existing line data"
+ with pytest.raises(CoverageException, match=msg):
covdata.add_arcs(ARCS_3)
def test_cant_add_lines_with_arcs(self):
covdata = CoverageData()
covdata.add_arcs(ARCS_3)
- with self.assertRaisesRegex(CoverageException, "Can't add lines to existing arc data"):
+ msg = "Can't add line measurements to existing branch data"
+ with pytest.raises(CoverageException, match=msg):
covdata.add_lines(LINES_1)
def test_touch_file_with_lines(self):
@@ -183,34 +187,32 @@ def test_set_query_contexts(self):
covdata.set_context('test_a')
covdata.add_lines(LINES_1)
covdata.set_query_contexts(['test_*'])
- self.assertEqual(covdata.lines('a.py'), [1, 2])
+ assert covdata.lines('a.py') == [1, 2]
covdata.set_query_contexts(['other*'])
- self.assertEqual(covdata.lines('a.py'), [])
+ assert covdata.lines('a.py') == []
def test_no_lines_vs_unmeasured_file(self):
covdata = CoverageData()
covdata.add_lines(LINES_1)
covdata.touch_file('zzz.py')
- self.assertEqual(covdata.lines('zzz.py'), [])
- self.assertIsNone(covdata.lines('no_such_file.py'))
+ assert covdata.lines('zzz.py') == []
+ assert covdata.lines('no_such_file.py') is None
def test_lines_with_contexts(self):
covdata = CoverageData()
covdata.set_context('test_a')
covdata.add_lines(LINES_1)
- self.assertEqual(covdata.lines('a.py'), [1, 2])
+ assert covdata.lines('a.py') == [1, 2]
covdata.set_query_contexts(['test*'])
- self.assertEqual(covdata.lines('a.py'), [1, 2])
+ assert covdata.lines('a.py') == [1, 2]
covdata.set_query_contexts(['other*'])
- self.assertEqual(covdata.lines('a.py'), [])
+ assert covdata.lines('a.py') == []
def test_contexts_by_lineno_with_lines(self):
covdata = CoverageData()
covdata.set_context('test_a')
covdata.add_lines(LINES_1)
- self.assertDictEqual(
- covdata.contexts_by_lineno('a.py'),
- {1: ['test_a'], 2: ['test_a']})
+ assert covdata.contexts_by_lineno('a.py') == {1: ['test_a'], 2: ['test_a']}
def test_no_duplicate_lines(self):
covdata = CoverageData()
@@ -218,7 +220,7 @@ def test_no_duplicate_lines(self):
covdata.add_lines(LINES_1)
covdata.set_context("context2")
covdata.add_lines(LINES_1)
- self.assertEqual(covdata.lines('a.py'), A_PY_LINES_1)
+ assert covdata.lines('a.py') == A_PY_LINES_1
def test_no_duplicate_arcs(self):
covdata = CoverageData()
@@ -226,39 +228,37 @@ def test_no_duplicate_arcs(self):
covdata.add_arcs(ARCS_3)
covdata.set_context("context2")
covdata.add_arcs(ARCS_3)
- self.assertEqual(covdata.arcs('x.py'), X_PY_ARCS_3)
+ assert covdata.arcs('x.py') == X_PY_ARCS_3
def test_no_arcs_vs_unmeasured_file(self):
covdata = CoverageData()
covdata.add_arcs(ARCS_3)
covdata.touch_file('zzz.py')
- self.assertEqual(covdata.lines('zzz.py'), [])
- self.assertIsNone(covdata.lines('no_such_file.py'))
- self.assertEqual(covdata.arcs('zzz.py'), [])
- self.assertIsNone(covdata.arcs('no_such_file.py'))
+ assert covdata.lines('zzz.py') == []
+ assert covdata.lines('no_such_file.py') is None
+ assert covdata.arcs('zzz.py') == []
+ assert covdata.arcs('no_such_file.py') is None
def test_arcs_with_contexts(self):
covdata = CoverageData()
covdata.set_context('test_x')
covdata.add_arcs(ARCS_3)
- self.assertEqual(covdata.arcs('x.py'), [(-1, 1), (1, 2), (2, 3), (3, -1)])
+ assert covdata.arcs('x.py') == [(-1, 1), (1, 2), (2, 3), (3, -1)]
covdata.set_query_contexts(['test*'])
- self.assertEqual(covdata.arcs('x.py'), [(-1, 1), (1, 2), (2, 3), (3, -1)])
+ assert covdata.arcs('x.py') == [(-1, 1), (1, 2), (2, 3), (3, -1)]
covdata.set_query_contexts(['other*'])
- self.assertEqual(covdata.arcs('x.py'), [])
+ assert covdata.arcs('x.py') == []
def test_contexts_by_lineno_with_arcs(self):
covdata = CoverageData()
covdata.set_context('test_x')
covdata.add_arcs(ARCS_3)
- self.assertDictEqual(
- covdata.contexts_by_lineno('x.py'),
- {-1: ['test_x'], 1: ['test_x'], 2: ['test_x'], 3: ['test_x']})
+ expected = {-1: ['test_x'], 1: ['test_x'], 2: ['test_x'], 3: ['test_x']}
+ assert expected == covdata.contexts_by_lineno('x.py')
def test_contexts_by_lineno_with_unknown_file(self):
covdata = CoverageData()
- self.assertDictEqual(
- covdata.contexts_by_lineno('xyz.py'), {})
+ assert covdata.contexts_by_lineno('xyz.py') == {}
def test_file_tracer_name(self):
covdata = CoverageData()
@@ -268,18 +268,18 @@ def test_file_tracer_name(self):
"main.py": dict.fromkeys([20]),
})
covdata.add_file_tracers({"p1.foo": "p1.plugin", "p2.html": "p2.plugin"})
- self.assertEqual(covdata.file_tracer("p1.foo"), "p1.plugin")
- self.assertEqual(covdata.file_tracer("main.py"), "")
- self.assertIsNone(covdata.file_tracer("p3.not_here"))
+ assert covdata.file_tracer("p1.foo") == "p1.plugin"
+ assert covdata.file_tracer("main.py") == ""
+ assert covdata.file_tracer("p3.not_here") is None
def test_cant_file_tracer_unmeasured_files(self):
covdata = CoverageData()
msg = "Can't add file tracer data for unmeasured file 'p1.foo'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata.add_file_tracers({"p1.foo": "p1.plugin"})
covdata.add_lines({"p2.html": dict.fromkeys([10, 11, 12])})
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata.add_file_tracers({"p1.foo": "p1.plugin"})
def test_cant_change_file_tracer_name(self):
@@ -288,7 +288,7 @@ def test_cant_change_file_tracer_name(self):
covdata.add_file_tracers({"p1.foo": "p1.plugin"})
msg = "Conflicting file tracer name for 'p1.foo': u?'p1.plugin' vs u?'p1.plugin.foo'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata.add_file_tracers({"p1.foo": "p1.plugin.foo"})
def test_update_lines(self):
@@ -326,10 +326,10 @@ def test_update_cant_mix_lines_and_arcs(self):
covdata2 = CoverageData(suffix='2')
covdata2.add_arcs(ARCS_3)
- with self.assertRaisesRegex(CoverageException, "Can't combine arc data with line data"):
+ with pytest.raises(CoverageException, match="Can't combine arc data with line data"):
covdata1.update(covdata2)
- with self.assertRaisesRegex(CoverageException, "Can't combine line data with arc data"):
+ with pytest.raises(CoverageException, match="Can't combine line data with arc data"):
covdata2.update(covdata1)
def test_update_file_tracers(self):
@@ -360,10 +360,10 @@ def test_update_file_tracers(self):
covdata3 = CoverageData(suffix='3')
covdata3.update(covdata1)
covdata3.update(covdata2)
- self.assertEqual(covdata3.file_tracer("p1.html"), "html.plugin")
- self.assertEqual(covdata3.file_tracer("p2.html"), "html.plugin2")
- self.assertEqual(covdata3.file_tracer("p3.foo"), "foo_plugin")
- self.assertEqual(covdata3.file_tracer("main.py"), "")
+ assert covdata3.file_tracer("p1.html") == "html.plugin"
+ assert covdata3.file_tracer("p2.html") == "html.plugin2"
+ assert covdata3.file_tracer("p3.foo") == "foo_plugin"
+ assert covdata3.file_tracer("main.py") == ""
def test_update_conflicting_file_tracers(self):
covdata1 = CoverageData(suffix='1')
@@ -375,11 +375,11 @@ def test_update_conflicting_file_tracers(self):
covdata2.add_file_tracers({"p1.html": "html.other_plugin"})
msg = "Conflicting file tracer name for 'p1.html': u?'html.plugin' vs u?'html.other_plugin'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata1.update(covdata2)
msg = "Conflicting file tracer name for 'p1.html': u?'html.other_plugin' vs u?'html.plugin'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata2.update(covdata1)
def test_update_file_tracer_vs_no_file_tracer(self):
@@ -391,11 +391,11 @@ def test_update_file_tracer_vs_no_file_tracer(self):
covdata2.add_lines({"p1.html": dict.fromkeys([1, 2, 3])})
msg = "Conflicting file tracer name for 'p1.html': u?'html.plugin' vs u?''"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata1.update(covdata2)
msg = "Conflicting file tracer name for 'p1.html': u?'' vs u?'html.plugin'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata2.update(covdata1)
def test_update_lines_empty(self):
@@ -418,7 +418,7 @@ 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)
+ assert covdata.arcs("missing.py") is None
self.assert_measured_files(covdata, [])
def test_add_to_hash_with_lines(self):
@@ -426,10 +426,10 @@ def test_add_to_hash_with_lines(self):
covdata.add_lines(LINES_1)
hasher = mock.Mock()
add_data_to_hash(covdata, "a.py", hasher)
- self.assertEqual(hasher.method_calls, [
+ assert hasher.method_calls == [
mock.call.update([1, 2]), # lines
mock.call.update(""), # file_tracer name
- ])
+ ]
def test_add_to_hash_with_arcs(self):
covdata = CoverageData()
@@ -437,10 +437,10 @@ def test_add_to_hash_with_arcs(self):
covdata.add_file_tracers({"y.py": "hologram_plugin"})
hasher = mock.Mock()
add_data_to_hash(covdata, "y.py", hasher)
- self.assertEqual(hasher.method_calls, [
+ assert hasher.method_calls == [
mock.call.update([(-1, 17), (17, 23), (23, -1)]), # arcs
mock.call.update("hologram_plugin"), # file_tracer name
- ])
+ ]
def test_add_to_lines_hash_with_missing_file(self):
# https://github.com/nedbat/coveragepy/issues/403
@@ -448,10 +448,10 @@ def test_add_to_lines_hash_with_missing_file(self):
covdata.add_lines(LINES_1)
hasher = mock.Mock()
add_data_to_hash(covdata, "missing.py", hasher)
- self.assertEqual(hasher.method_calls, [
+ assert hasher.method_calls == [
mock.call.update([]),
mock.call.update(None),
- ])
+ ]
def test_add_to_arcs_hash_with_missing_file(self):
# https://github.com/nedbat/coveragepy/issues/403
@@ -460,22 +460,22 @@ def test_add_to_arcs_hash_with_missing_file(self):
covdata.add_file_tracers({"y.py": "hologram_plugin"})
hasher = mock.Mock()
add_data_to_hash(covdata, "missing.py", hasher)
- self.assertEqual(hasher.method_calls, [
+ assert hasher.method_calls == [
mock.call.update([]),
mock.call.update(None),
- ])
+ ]
def test_empty_lines_are_still_lines(self):
covdata = CoverageData()
covdata.add_lines({})
covdata.touch_file("abc.py")
- self.assertFalse(covdata.has_arcs())
+ assert not covdata.has_arcs()
def test_empty_arcs_are_still_arcs(self):
covdata = CoverageData()
covdata.add_arcs({})
covdata.touch_file("abc.py")
- self.assertTrue(covdata.has_arcs())
+ assert covdata.has_arcs()
def test_read_and_write_are_opposites(self):
covdata1 = CoverageData()
@@ -527,34 +527,34 @@ def test_read_errors(self):
msg = r"Couldn't .* '.*[/\\]{0}': \S+"
self.make_file("xyzzy.dat", "xyzzy")
- with self.assertRaisesRegex(CoverageException, msg.format("xyzzy.dat")):
+ with pytest.raises(CoverageException, match=msg.format("xyzzy.dat")):
covdata = CoverageData("xyzzy.dat")
covdata.read()
- self.assertFalse(covdata)
+ assert not covdata
self.make_file("empty.dat", "")
- with self.assertRaisesRegex(CoverageException, msg.format("empty.dat")):
+ with pytest.raises(CoverageException, match=msg.format("empty.dat")):
covdata = CoverageData("empty.dat")
covdata.read()
- self.assertFalse(covdata)
+ assert not covdata
def test_read_sql_errors(self):
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 .* '.*[/\\]{}': wrong schema: 99 instead of \d+".format("wrong_schema.db")
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata = CoverageData("wrong_schema.db")
covdata.read()
- self.assertFalse(covdata)
+ assert not covdata
with sqlite3.connect("no_schema.db") as con:
con.execute("create table foobar (baz text)")
msg = r"Couldn't .* '.*[/\\]{}': \S+".format("no_schema.db")
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata = CoverageData("no_schema.db")
covdata.read()
- self.assertFalse(covdata)
+ assert not covdata
class CoverageDataFilesTest(DataTestHelpers, CoverageTest):
@@ -589,11 +589,11 @@ def test_debug_output_with_debug_option(self):
covdata2.read()
self.assert_line_counts(covdata2, SUMMARY_1)
- self.assertRegex(
- debug.get_output(),
+ assert re.search(
r"^Erasing data file '.*\.coverage'\n"
r"Creating data file '.*\.coverage'\n"
- r"Opening data file '.*\.coverage'\n$"
+ r"Opening data file '.*\.coverage'\n$",
+ debug.get_output()
)
def test_debug_output_without_debug_option(self):
@@ -608,7 +608,7 @@ def test_debug_output_without_debug_option(self):
covdata2.read()
self.assert_line_counts(covdata2, SUMMARY_1)
- self.assertEqual(debug.get_output(), "")
+ assert debug.get_output() == ""
def test_explicit_suffix(self):
self.assert_doesnt_exist(".coverage.SUFFIX")
@@ -627,7 +627,7 @@ def test_true_suffix(self):
covdata1.write()
self.assert_doesnt_exist(".coverage")
data_files1 = glob.glob(".coverage.*")
- self.assertEqual(len(data_files1), 1)
+ assert len(data_files1) == 1
# Another suffix=True will choose a different name.
covdata2 = CoverageData(suffix=True)
@@ -635,10 +635,10 @@ def test_true_suffix(self):
covdata2.write()
self.assert_doesnt_exist(".coverage")
data_files2 = glob.glob(".coverage.*")
- self.assertEqual(len(data_files2), 2)
+ assert len(data_files2) == 2
# In addition to being different, the suffixes have the pid in them.
- self.assertTrue(all(str(os.getpid()) in fn for fn in data_files2))
+ assert all(str(os.getpid()) in fn for fn in data_files2)
def test_combining(self):
self.assert_file_count(".coverage.*", 0)
@@ -718,7 +718,7 @@ def test_combining_with_aliases(self):
self.assert_line_counts(covdata3, {apy: 4, sub_bpy: 2, template_html: 1}, fullpath=True)
self.assert_measured_files(covdata3, [apy, sub_bpy, template_html])
- self.assertEqual(covdata3.file_tracer(template_html), 'html.plugin')
+ assert covdata3.file_tracer(template_html) == 'html.plugin'
def test_combining_from_different_directories(self):
os.makedirs('cov1')
@@ -778,7 +778,7 @@ def test_combining_from_files(self):
def test_combining_from_nonexistent_directories(self):
covdata = CoverageData()
msg = "Couldn't combine from non-existent path 'xyzzy'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
combine_parallel_data(covdata, data_paths=['xyzzy'])
def test_interleaved_erasing_bug716(self):
@@ -817,5 +817,5 @@ def test_misfed_serialization(self):
re.escape(repr(bad_data[:40])),
len(bad_data),
)
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
covdata.loads(bad_data)
diff --git a/tests/test_debug.py b/tests/test_debug.py
index 228e33b0c..55001c96a 100644
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -4,14 +4,15 @@
"""Tests of coverage/debug.py"""
import os
+import re
import pytest
import coverage
+from coverage import env
from coverage.backward import StringIO
from coverage.debug import filter_text, info_formatter, info_header, short_id, short_stack
from coverage.debug import clipped_repr
-from coverage.env import C_TRACER
from tests.coveragetest import CoverageTest
from tests.helpers import re_line, re_lines
@@ -38,7 +39,7 @@ def test_info_formatter(self):
' jkl',
' nothing: -none-',
]
- self.assertEqual(expected, lines)
+ assert expected == lines
def test_info_formatter_with_generator(self):
lines = list(info_formatter(('info%d' % i, i) for i in range(3)))
@@ -47,10 +48,10 @@ def test_info_formatter_with_generator(self):
' info1: 1',
' info2: 2',
]
- self.assertEqual(expected, lines)
+ assert expected == lines
def test_too_long_label(self):
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
list(info_formatter([('this label is way too long and will not fit', 23)]))
@@ -118,20 +119,20 @@ def test_debug_no_trace(self):
out_lines = self.f1_debug_output([])
# We should have no output at all.
- self.assertFalse(out_lines)
+ assert not out_lines
def test_debug_trace(self):
out_lines = self.f1_debug_output(["trace"])
# We should have a line like "Tracing 'f1.py'"
- self.assertIn("Tracing 'f1.py'", out_lines)
+ assert "Tracing 'f1.py'" in out_lines
# We should have lines like "Not tracing 'collector.py'..."
coverage_lines = re_lines(
out_lines,
r"^Not tracing .*: is part of coverage.py$"
)
- self.assertTrue(coverage_lines)
+ assert coverage_lines
def test_debug_trace_pid(self):
out_lines = self.f1_debug_output(["trace", "pid"])
@@ -139,11 +140,11 @@ def test_debug_trace_pid(self):
# Now our lines are always prefixed with the process id.
pid_prefix = r"^%5d\.[0-9a-f]{4}: " % os.getpid()
pid_lines = re_lines(out_lines, pid_prefix)
- self.assertEqual(pid_lines, out_lines)
+ assert pid_lines == out_lines
# We still have some tracing, and some not tracing.
- self.assertTrue(re_lines(out_lines, pid_prefix + "Tracing "))
- self.assertTrue(re_lines(out_lines, pid_prefix + "Not tracing "))
+ assert re_lines(out_lines, pid_prefix + "Tracing ")
+ assert re_lines(out_lines, pid_prefix + "Not tracing ")
def test_debug_callers(self):
out_lines = self.f1_debug_output(["pid", "dataop", "dataio", "callers"])
@@ -153,15 +154,15 @@ def test_debug_callers(self):
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))
+ assert len(real_messages) == len(frames)
last_line = out_lines.splitlines()[-1]
# The details of what to expect on the stack are empirical, and can change
# as the code changes. This test is here to ensure that the debug code
# continues working. It's ok to adjust these details over time.
- self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Adding file tracers: 0 files")
- self.assertRegex(last_line, r"\s+add_file_tracers : .*coverage[/\\]sqldata.py:\d+$")
+ assert re.search(r"^\s*\d+\.\w{4}: Adding file tracers: 0 files", real_messages[-1])
+ assert re.search(r"\s+add_file_tracers : .*coverage[/\\]sqldata.py:\d+$", last_line)
def test_debug_config(self):
out_lines = self.f1_debug_output(["config"])
@@ -175,11 +176,8 @@ def test_debug_config(self):
""".split()
for label in labels:
label_pat = r"^\s*%s: " % label
- self.assertEqual(
- len(re_lines(out_lines, label_pat).splitlines()),
- 1,
- msg="Incorrect lines for %r" % label,
- )
+ msg = "Incorrect lines for %r" % label
+ assert 1 == len(re_lines(out_lines, label_pat).splitlines()), msg
def test_debug_sys(self):
out_lines = self.f1_debug_output(["sys"])
@@ -191,20 +189,17 @@ def test_debug_sys(self):
""".split()
for label in labels:
label_pat = r"^\s*%s: " % label
- self.assertEqual(
- len(re_lines(out_lines, label_pat).splitlines()),
- 1,
- msg="Incorrect lines for %r" % label,
- )
+ msg = "Incorrect lines for %r" % label
+ assert 1 == len(re_lines(out_lines, label_pat).splitlines()), msg
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:
+ if env.C_TRACER:
expected = "CTracer: available"
else:
expected = "CTracer: unavailable"
- self.assertEqual(expected, tracer_line)
+ assert expected == tracer_line
def f_one(*args, **kwargs):
@@ -227,15 +222,15 @@ class ShortStackTest(CoverageTest):
def test_short_stack(self):
stack = f_one().splitlines()
- self.assertGreater(len(stack), 10)
- self.assertIn("f_three", stack[-1])
- self.assertIn("f_two", stack[-2])
- self.assertIn("f_one", stack[-3])
+ assert len(stack) > 10
+ assert "f_three" in stack[-1]
+ assert "f_two" in stack[-2]
+ assert "f_one" in stack[-3]
def test_short_stack_limit(self):
stack = f_one(limit=5).splitlines()
- self.assertEqual(len(stack), 5)
+ assert len(stack) == 5
def test_short_stack_skip(self):
stack = f_one(skip=1).splitlines()
- self.assertIn("f_two", stack[-1])
+ assert "f_two" in stack[-1]
diff --git a/tests/test_execfile.py b/tests/test_execfile.py
index 5a718aaef..3cdd1ed9b 100644
--- a/tests/test_execfile.py
+++ b/tests/test_execfile.py
@@ -11,6 +11,8 @@
import re
import sys
+import pytest
+
from coverage import env
from coverage.backward import binary_bytes
from coverage.execfile import run_python_file, run_python_module
@@ -30,27 +32,26 @@ def test_run_python_file(self):
mod_globs = json.loads(self.stdout())
# The file should think it is __main__
- self.assertEqual(mod_globs['__name__'], "__main__")
+ assert mod_globs['__name__'] == "__main__"
# It should seem to come from a file named try_execfile.py
dunder_file = os.path.basename(mod_globs['__file__'])
- self.assertEqual(dunder_file, "try_execfile.py")
+ assert dunder_file == "try_execfile.py"
# It should have its correct module data.
- self.assertEqual(mod_globs['__doc__'].splitlines()[0],
- "Test file for run_python_file.")
- self.assertEqual(mod_globs['DATA'], "xyzzy")
- self.assertEqual(mod_globs['FN_VAL'], "my_fn('fooey')")
+ assert mod_globs['__doc__'].splitlines()[0] == "Test file for run_python_file."
+ assert mod_globs['DATA'] == "xyzzy"
+ assert mod_globs['FN_VAL'] == "my_fn('fooey')"
# It must be self-importable as __main__.
- self.assertEqual(mod_globs['__main__.DATA'], "xyzzy")
+ assert mod_globs['__main__.DATA'] == "xyzzy"
# Argv should have the proper values.
- self.assertEqual(mod_globs['argv0'], TRY_EXECFILE)
- self.assertEqual(mod_globs['argv1-n'], ["arg1", "arg2"])
+ assert mod_globs['argv0'] == TRY_EXECFILE
+ assert mod_globs['argv1-n'] == ["arg1", "arg2"]
# __builtins__ should have the right values, like open().
- self.assertEqual(mod_globs['__builtins__.has_open'], True)
+ assert mod_globs['__builtins__.has_open'] is True
def test_no_extra_file(self):
# Make sure that running a file doesn't create an extra compiled file.
@@ -58,9 +59,9 @@ def test_no_extra_file(self):
desc = "a non-.py file!"
""")
- self.assertEqual(os.listdir("."), ["xxx"])
+ assert os.listdir(".") == ["xxx"]
run_python_file(["xxx"])
- self.assertEqual(os.listdir("."), ["xxx"])
+ assert os.listdir(".") == ["xxx"]
def test_universal_newlines(self):
# Make sure we can read any sort of line ending.
@@ -69,7 +70,7 @@ def test_universal_newlines(self):
with open('nl.py', 'wb') as fpy:
fpy.write(nl.join(pylines).encode('utf-8'))
run_python_file(['nl.py'])
- self.assertEqual(self.stdout(), "Hello, world!\n"*3)
+ assert self.stdout() == "Hello, world!\n"*3
def test_missing_final_newline(self):
# Make sure we can deal with a Python file with no final newline.
@@ -80,14 +81,14 @@ def test_missing_final_newline(self):
#""")
with open("abrupt.py") as f:
abrupt = f.read()
- self.assertEqual(abrupt[-1], '#')
+ assert abrupt[-1] == '#'
run_python_file(["abrupt.py"])
- self.assertEqual(self.stdout(), "a is 1\n")
+ assert self.stdout() == "a is 1\n"
def test_no_such_file(self):
path = python_reported_file('xyzzy.py')
msg = re.escape("No file to run: '{}'".format(path))
- with self.assertRaisesRegex(NoSource, msg):
+ with pytest.raises(NoSource, match=msg):
run_python_file(["xyzzy.py"])
def test_directory_with_main(self):
@@ -95,11 +96,11 @@ def test_directory_with_main(self):
print("I am __main__")
""")
run_python_file(["with_main"])
- self.assertEqual(self.stdout(), "I am __main__\n")
+ assert self.stdout() == "I am __main__\n"
def test_directory_without_main(self):
self.make_file("without_main/__init__.py", "")
- with self.assertRaisesRegex(NoSource, "Can't find '__main__' module in 'without_main'"):
+ with pytest.raises(NoSource, match="Can't find '__main__' module in 'without_main'"):
run_python_file(["without_main"])
@@ -109,7 +110,7 @@ class RunPycFileTest(CoverageTest):
def make_pyc(self): # pylint: disable=inconsistent-return-statements
"""Create a .pyc file, and return the path to it."""
if env.JYTHON:
- self.skipTest("Can't make .pyc files on Jython")
+ pytest.skip("Can't make .pyc files on Jython")
self.make_file("compiled.py", """\
def doit():
@@ -134,15 +135,15 @@ def doit():
def test_running_pyc(self):
pycfile = self.make_pyc()
run_python_file([pycfile])
- self.assertEqual(self.stdout(), "I am here!\n")
+ assert self.stdout() == "I am here!\n"
def test_running_pyo(self):
pycfile = self.make_pyc()
pyofile = re.sub(r"[.]pyc$", ".pyo", pycfile)
- self.assertNotEqual(pycfile, pyofile)
+ assert pycfile != pyofile
os.rename(pycfile, pyofile)
run_python_file([pyofile])
- self.assertEqual(self.stdout(), "I am here!\n")
+ assert self.stdout() == "I am here!\n"
def test_running_pyc_from_wrong_python(self):
pycfile = self.make_pyc()
@@ -152,7 +153,7 @@ def test_running_pyc_from_wrong_python(self):
fpyc.seek(0)
fpyc.write(binary_bytes([0x2a, 0xeb, 0x0d, 0x0a]))
- with self.assertRaisesRegex(NoCode, "Bad magic number in .pyc file"):
+ with pytest.raises(NoCode, match="Bad magic number in .pyc file"):
run_python_file([pycfile])
# In some environments, the pycfile persists and pollutes another test.
@@ -161,7 +162,7 @@ def test_running_pyc_from_wrong_python(self):
def test_no_such_pyc_file(self):
path = python_reported_file('xyzzy.pyc')
msg = re.escape("No file to run: '{}'".format(path))
- with self.assertRaisesRegex(NoCode, msg):
+ with pytest.raises(NoCode, match=msg):
run_python_file(["xyzzy.pyc"])
def test_running_py_from_binary(self):
@@ -181,7 +182,7 @@ def test_running_py_from_binary(self):
r"source code string cannot contain null bytes" # for py3
r")"
)
- with self.assertRaisesRegex(Exception, msg):
+ with pytest.raises(Exception, match=msg):
run_python_file([bf])
@@ -192,42 +193,48 @@ class RunModuleTest(UsingModulesMixin, CoverageTest):
def test_runmod1(self):
run_python_module(["runmod1", "hello"])
- self.assertEqual(self.stderr(), "")
- self.assertEqual(self.stdout(), "runmod1: passed hello\n")
+ out, err = self.stdouterr()
+ assert out == "runmod1: passed hello\n"
+ assert err == ""
def test_runmod2(self):
run_python_module(["pkg1.runmod2", "hello"])
- self.assertEqual(self.stderr(), "")
- self.assertEqual(self.stdout(), "pkg1.__init__: pkg1\nrunmod2: passed hello\n")
+ out, err = self.stdouterr()
+ assert out == "pkg1.__init__: pkg1\nrunmod2: passed hello\n"
+ assert err == ""
def test_runmod3(self):
run_python_module(["pkg1.sub.runmod3", "hello"])
- self.assertEqual(self.stderr(), "")
- self.assertEqual(self.stdout(), "pkg1.__init__: pkg1\nrunmod3: passed hello\n")
+ out, err = self.stdouterr()
+ assert out == "pkg1.__init__: pkg1\nrunmod3: passed hello\n"
+ assert err == ""
def test_pkg1_main(self):
run_python_module(["pkg1", "hello"])
- self.assertEqual(self.stderr(), "")
- self.assertEqual(self.stdout(), "pkg1.__init__: pkg1\npkg1.__main__: passed hello\n")
+ out, err = self.stdouterr()
+ assert out == "pkg1.__init__: pkg1\npkg1.__main__: passed hello\n"
+ assert err == ""
def test_pkg1_sub_main(self):
run_python_module(["pkg1.sub", "hello"])
- self.assertEqual(self.stderr(), "")
- self.assertEqual(self.stdout(), "pkg1.__init__: pkg1\npkg1.sub.__main__: passed hello\n")
+ out, err = self.stdouterr()
+ assert out == "pkg1.__init__: pkg1\npkg1.sub.__main__: passed hello\n"
+ assert err == ""
def test_pkg1_init(self):
run_python_module(["pkg1.__init__", "wut?"])
- self.assertEqual(self.stderr(), "")
- self.assertEqual(self.stdout(), "pkg1.__init__: pkg1\npkg1.__init__: __main__\n")
+ out, err = self.stdouterr()
+ assert out == "pkg1.__init__: pkg1\npkg1.__init__: __main__\n"
+ assert err == ""
def test_no_such_module(self):
- with self.assertRaisesRegex(NoSource, "No module named '?i_dont_exist'?"):
+ with pytest.raises(NoSource, match="No module named '?i_dont_exist'?"):
run_python_module(["i_dont_exist"])
- with self.assertRaisesRegex(NoSource, "No module named '?i'?"):
+ with pytest.raises(NoSource, match="No module named '?i'?"):
run_python_module(["i.dont_exist"])
- with self.assertRaisesRegex(NoSource, "No module named '?i'?"):
+ with pytest.raises(NoSource, match="No module named '?i'?"):
run_python_module(["i.dont.exist"])
def test_no_main(self):
- with self.assertRaises(NoSource):
+ with pytest.raises(NoSource):
run_python_module(["pkg2", "hi"])
diff --git a/tests/test_filereporter.py b/tests/test_filereporter.py
index eb1e91e88..d928eea40 100644
--- a/tests/test_filereporter.py
+++ b/tests/test_filereporter.py
@@ -28,23 +28,23 @@ def test_filenames(self):
acu = PythonFileReporter("aa/afile.py")
bcu = PythonFileReporter("aa/bb/bfile.py")
ccu = PythonFileReporter("aa/bb/cc/cfile.py")
- self.assertEqual(acu.relative_filename(), "aa/afile.py")
- self.assertEqual(bcu.relative_filename(), "aa/bb/bfile.py")
- self.assertEqual(ccu.relative_filename(), "aa/bb/cc/cfile.py")
- self.assertEqual(acu.source(), "# afile.py\n")
- self.assertEqual(bcu.source(), "# bfile.py\n")
- self.assertEqual(ccu.source(), "# cfile.py\n")
+ assert acu.relative_filename() == "aa/afile.py"
+ assert bcu.relative_filename() == "aa/bb/bfile.py"
+ assert ccu.relative_filename() == "aa/bb/cc/cfile.py"
+ assert acu.source() == "# afile.py\n"
+ assert bcu.source() == "# bfile.py\n"
+ assert ccu.source() == "# cfile.py\n"
def test_odd_filenames(self):
acu = PythonFileReporter("aa/afile.odd.py")
bcu = PythonFileReporter("aa/bb/bfile.odd.py")
b2cu = PythonFileReporter("aa/bb.odd/bfile.py")
- self.assertEqual(acu.relative_filename(), "aa/afile.odd.py")
- self.assertEqual(bcu.relative_filename(), "aa/bb/bfile.odd.py")
- self.assertEqual(b2cu.relative_filename(), "aa/bb.odd/bfile.py")
- self.assertEqual(acu.source(), "# afile.odd.py\n")
- self.assertEqual(bcu.source(), "# bfile.odd.py\n")
- self.assertEqual(b2cu.source(), "# bfile.py\n")
+ assert acu.relative_filename() == "aa/afile.odd.py"
+ assert bcu.relative_filename() == "aa/bb/bfile.odd.py"
+ assert b2cu.relative_filename() == "aa/bb.odd/bfile.py"
+ assert acu.source() == "# afile.odd.py\n"
+ assert bcu.source() == "# bfile.odd.py\n"
+ assert b2cu.source() == "# bfile.py\n"
def test_modules(self):
import aa
@@ -54,12 +54,12 @@ def test_modules(self):
acu = PythonFileReporter(aa)
bcu = PythonFileReporter(aa.bb)
ccu = PythonFileReporter(aa.bb.cc)
- self.assertEqual(acu.relative_filename(), native("aa/__init__.py"))
- self.assertEqual(bcu.relative_filename(), native("aa/bb/__init__.py"))
- self.assertEqual(ccu.relative_filename(), native("aa/bb/cc/__init__.py"))
- self.assertEqual(acu.source(), "# aa\n")
- self.assertEqual(bcu.source(), "# bb\n")
- self.assertEqual(ccu.source(), "") # yes, empty
+ assert acu.relative_filename() == native("aa/__init__.py")
+ assert bcu.relative_filename() == native("aa/bb/__init__.py")
+ assert ccu.relative_filename() == native("aa/bb/cc/__init__.py")
+ assert acu.source() == "# aa\n"
+ assert bcu.source() == "# bb\n"
+ assert ccu.source() == "" # yes, empty
def test_module_files(self):
import aa.afile
@@ -69,12 +69,12 @@ def test_module_files(self):
acu = PythonFileReporter(aa.afile)
bcu = PythonFileReporter(aa.bb.bfile)
ccu = PythonFileReporter(aa.bb.cc.cfile)
- self.assertEqual(acu.relative_filename(), native("aa/afile.py"))
- self.assertEqual(bcu.relative_filename(), native("aa/bb/bfile.py"))
- self.assertEqual(ccu.relative_filename(), native("aa/bb/cc/cfile.py"))
- self.assertEqual(acu.source(), "# afile.py\n")
- self.assertEqual(bcu.source(), "# bfile.py\n")
- self.assertEqual(ccu.source(), "# cfile.py\n")
+ assert acu.relative_filename() == native("aa/afile.py")
+ assert bcu.relative_filename() == native("aa/bb/bfile.py")
+ assert ccu.relative_filename() == native("aa/bb/cc/cfile.py")
+ assert acu.source() == "# afile.py\n"
+ assert bcu.source() == "# bfile.py\n"
+ assert ccu.source() == "# cfile.py\n"
def test_comparison(self):
acu = FileReporter("aa/afile.py")
@@ -100,5 +100,5 @@ def test_egg(self):
ecu = PythonFileReporter(egg1)
eecu = PythonFileReporter(egg1.egg1)
- self.assertEqual(ecu.source(), u"")
- self.assertIn(u"# My egg file!", eecu.source().splitlines())
+ assert ecu.source() == u""
+ assert u"# My egg file!" in eecu.source().splitlines()
diff --git a/tests/test_files.py b/tests/test_files.py
index 84e25f107..6040b8898 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -30,10 +30,10 @@ def abs_path(self, p):
def test_simple(self):
self.make_file("hello.py")
files.set_relative_directory()
- self.assertEqual(files.relative_filename(u"hello.py"), u"hello.py")
+ assert files.relative_filename(u"hello.py") == u"hello.py"
a = self.abs_path("hello.py")
- self.assertNotEqual(a, "hello.py")
- self.assertEqual(files.relative_filename(a), "hello.py")
+ assert a != "hello.py"
+ assert files.relative_filename(a) == "hello.py"
def test_peer_directories(self):
self.make_file("sub/proj1/file1.py")
@@ -43,8 +43,8 @@ def test_peer_directories(self):
d = os.path.normpath("sub/proj1")
self.chdir(d)
files.set_relative_directory()
- self.assertEqual(files.relative_filename(a1), "file1.py")
- self.assertEqual(files.relative_filename(a2), a2)
+ assert files.relative_filename(a1) == "file1.py"
+ assert files.relative_filename(a2) == a2
def test_filepath_contains_absolute_prefix_twice(self):
# https://github.com/nedbat/coveragepy/issues/194
@@ -55,7 +55,7 @@ def test_filepath_contains_absolute_prefix_twice(self):
d = abs_file(os.curdir)
trick = os.path.splitdrive(d)[1].lstrip(os.path.sep)
rel = os.path.join('sub', trick, 'file1.py')
- self.assertEqual(files.relative_filename(abs_file(rel)), rel)
+ assert files.relative_filename(abs_file(rel)) == rel
def test_canonical_filename_ensure_cache_hit(self):
self.make_file("sub/proj1/file1.py")
@@ -63,12 +63,10 @@ def test_canonical_filename_ensure_cache_hit(self):
self.chdir(d)
files.set_relative_directory()
canonical_path = files.canonical_filename('sub/proj1/file1.py')
- self.assertEqual(canonical_path, self.abs_path('file1.py'))
+ assert canonical_path == self.abs_path('file1.py')
# After the filename has been converted, it should be in the cache.
- self.assertIn('sub/proj1/file1.py', files.CANONICAL_FILENAME_CACHE)
- self.assertEqual(
- files.canonical_filename('sub/proj1/file1.py'),
- self.abs_path('file1.py'))
+ assert 'sub/proj1/file1.py' in files.CANONICAL_FILENAME_CACHE
+ assert files.canonical_filename('sub/proj1/file1.py') == self.abs_path('file1.py')
@pytest.mark.parametrize("original, flat", [
@@ -149,10 +147,8 @@ def setUp(self):
def assertMatches(self, matcher, filepath, matches):
"""The `matcher` should agree with `matches` about `filepath`."""
canonical = files.canonical_filename(filepath)
- self.assertEqual(
- matcher.match(canonical), matches,
- "File %s should have matched as %s" % (filepath, matches)
- )
+ msg = "File %s should have matched as %s" % (filepath, matches)
+ assert matches == matcher.match(canonical), msg
def test_tree_matcher(self):
matches_to_try = [
@@ -167,7 +163,7 @@ def test_tree_matcher(self):
files.canonical_filename("sub3/file4.py"),
]
tm = TreeMatcher(trees)
- self.assertEqual(tm.info(), trees)
+ assert tm.info() == trees
for filepath, matches in matches_to_try:
self.assertMatches(tm, filepath, matches)
@@ -190,16 +186,9 @@ def test_module_matcher(self):
]
modules = ['test', 'py.test', 'mymain']
mm = ModuleMatcher(modules)
- self.assertEqual(
- mm.info(),
- modules
- )
+ assert mm.info() == modules
for modulename, matches in matches_to_try:
- self.assertEqual(
- mm.match(modulename),
- matches,
- modulename,
- )
+ assert mm.match(modulename) == matches, modulename
def test_fnmatch_matcher(self):
matches_to_try = [
@@ -210,7 +199,7 @@ def test_fnmatch_matcher(self):
(self.make_file("sub3/file5.c"), False),
]
fnm = FnmatchMatcher(["*.py", "*/sub2/*"])
- self.assertEqual(fnm.info(), ["*.py", "*/sub2/*"])
+ assert fnm.info() == ["*.py", "*/sub2/*"]
for filepath, matches in matches_to_try:
self.assertMatches(fnm, filepath, matches)
@@ -244,11 +233,11 @@ def assert_mapped(self, aliases, inp, out):
aliases.pprint()
print(inp)
print(out)
- self.assertEqual(aliases.map(inp), files.canonical_filename(out))
+ assert aliases.map(inp) == files.canonical_filename(out)
def assert_unchanged(self, aliases, inp):
"""Assert that `inp` mapped through `aliases` is unchanged."""
- self.assertEqual(aliases.map(inp), inp)
+ assert aliases.map(inp) == inp
def test_noop(self):
aliases = PathAliases()
@@ -283,11 +272,11 @@ def test_multiple_patterns(self):
def test_cant_have_wildcard_at_end(self):
aliases = PathAliases()
msg = "Pattern must not end with wildcards."
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
aliases.add("/ned/home/*", "fooey")
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
aliases.add("/ned/home/*/", "fooey")
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
aliases.add("/ned/home/*/*/", "fooey")
def test_no_accidental_munging(self):
@@ -407,15 +396,11 @@ def test_find_python_files(self):
])
+@pytest.mark.skipif(not env.WINDOWS, reason="Only need to run Windows tests on Windows.")
class WindowsFileTest(CoverageTest):
"""Windows-specific tests of file name handling."""
run_in_temp_dir = False
- def setUp(self):
- if not env.WINDOWS:
- self.skipTest("Only need to run Windows tests on Windows.")
- super(WindowsFileTest, self).setUp()
-
def test_actual_path(self):
- self.assertEqual(actual_path(r'c:\Windows'), actual_path(r'C:\wINDOWS'))
+ assert actual_path(r'c:\Windows') == actual_path(r'C:\wINDOWS')
diff --git a/tests/test_html.py b/tests/test_html.py
index 825b0afbe..51e0b93cd 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -13,6 +13,7 @@
import sys
import mock
+import pytest
from unittest_mixins import change_dir
import coverage
@@ -86,7 +87,7 @@ def assert_correct_timestamp(self, html):
"""Extract the timestamp from `html`, and assert it is recent."""
timestamp_pat = r"created at (\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})"
m = re.search(timestamp_pat, html)
- self.assertTrue(m, "Didn't find a timestamp!")
+ assert m, "Didn't find a timestamp!"
timestamp = datetime.datetime(*map(int, m.groups()))
# The timestamp only records the minute, so the delta could be from
# 12:00 to 12:01:59, or two minutes.
@@ -177,7 +178,7 @@ def func1(x): # A nice function
# Because the source change was only a comment, the index is the same.
index2 = self.get_html_index_content()
- self.assertMultiLineEqual(index1, index2)
+ assert index1 == index2
def test_html_delta_from_coverage_change(self):
# HTML generation can create only the files that have changed.
@@ -220,7 +221,7 @@ def test_html_delta_from_settings_change(self):
assert "htmlcov/main_file_py.html" in self.files_written
index2 = self.get_html_index_content()
- self.assertMultiLineEqual(index1, index2)
+ assert index1 == index2
def test_html_delta_from_coverage_version_change(self):
# HTML generation can create only the files that have changed.
@@ -244,7 +245,7 @@ def test_html_delta_from_coverage_version_change(self):
index2 = self.get_html_index_content()
fixed_index2 = index2.replace("XYZZY", self.real_coverage_version)
- self.assertMultiLineEqual(index1, fixed_index2)
+ assert index1 == fixed_index2
def test_file_becomes_100(self):
self.create_initial_files()
@@ -270,7 +271,7 @@ def test_status_format_change(self):
with open("htmlcov/status.json") as status_json:
status_data = json.load(status_json)
- self.assertEqual(status_data['format'], 2)
+ assert status_data['format'] == 2
status_data['format'] = 99
with open("htmlcov/status.json", "w") as status_json:
json.dump(status_data, status_json)
@@ -292,44 +293,36 @@ def test_default_title(self):
self.create_initial_files()
self.run_coverage()
index = self.get_html_index_content()
- self.assertIn("Coverage report", index)
- self.assertIn("Coverage report:", index)
+ assert "Coverage report" in index
+ assert "Coverage report:" in index
def test_title_set_in_config_file(self):
self.create_initial_files()
self.make_file(".coveragerc", "[html]\ntitle = Metrics & stuff!\n")
self.run_coverage()
index = self.get_html_index_content()
- self.assertIn("Metrics & stuff!", index)
- self.assertIn("Metrics & stuff!:", index)
+ assert "Metrics & stuff!" in index
+ assert "Metrics & stuff!:" in index
def test_non_ascii_title_set_in_config_file(self):
self.create_initial_files()
self.make_file(".coveragerc", "[html]\ntitle = «ταБЬℓσ» numbers")
self.run_coverage()
index = self.get_html_index_content()
- self.assertIn(
- "«ταБЬℓσ»"
- " numbers", index
- )
- self.assertIn(
- "«ταБЬℓσ»"
- " numbers", index
- )
+ assert "«ταБЬℓσ» numbers" in index
+ assert "«ταБЬℓσ» numbers" in index
def test_title_set_in_args(self):
self.create_initial_files()
self.make_file(".coveragerc", "[html]\ntitle = Good title\n")
self.run_coverage(htmlargs=dict(title="«ταБЬℓσ» & stüff!"))
index = self.get_html_index_content()
- self.assertIn(
- "«ταБЬℓσ»"
- " & stüff!", index
- )
- self.assertIn(
- "«ταБЬℓσ»"
- " & stüff!:", index
+ expected = (
+ "«ταБЬℓσ» " +
+ "& stüff!"
)
+ assert expected in index
+ assert "«ταБЬℓσ» & stüff!:" in index
class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest):
@@ -342,7 +335,7 @@ def test_dotpy_not_python(self):
self.start_import_stop(cov, "main")
self.make_file("innocuous.py", "This isn't python!
")
msg = "Couldn't parse '.*innocuous.py' as Python source: .* at line 1"
- with self.assertRaisesRegex(NotPython, msg):
+ with pytest.raises(NotPython, match=msg):
cov.html_report()
def test_dotpy_not_python_ignored(self):
@@ -352,20 +345,11 @@ def test_dotpy_not_python_ignored(self):
self.start_import_stop(cov, "main")
self.make_file("innocuous.py", "This isn't python!
")
cov.html_report(ignore_errors=True)
- self.assertEqual(
- len(cov._warnings),
- 1,
- "Expected a warning to be thrown when an invalid python file is parsed")
- self.assertIn(
- "Couldn't parse Python file",
- cov._warnings[0],
- "Warning message should be in 'invalid file' warning"
- )
- self.assertIn(
- "innocuous.py",
- cov._warnings[0],
- "Filename should be in 'invalid file' warning"
- )
+ msg = "Expected a warning to be thrown when an invalid python file is parsed"
+ assert 1 == len(cov._warnings), msg
+ msg = "Warning message should be in 'invalid file' warning"
+ assert "Couldn't parse Python file" in cov._warnings[0], msg
+ assert "innocuous.py" in cov._warnings[0], "Filename should be in 'invalid file' warning"
self.assert_exists("htmlcov/index.html")
# This would be better as a glob, if the HTML layout changes:
self.assert_doesnt_exist("htmlcov/innocuous.html")
@@ -380,7 +364,7 @@ def test_dothtml_not_python(self):
# Before reporting, change it to be an HTML file.
self.make_file("innocuous.html", "This isn't python at all!
")
output = self.run_command("coverage html")
- self.assertEqual(output.strip(), "No data to report.")
+ assert output.strip() == "No data to report."
def test_execed_liar_ignored(self):
# Jinja2 sets __file__ to be a non-Python file, and then execs code.
@@ -428,13 +412,13 @@ def test_decode_error(self):
with open("sub/not_ascii.py", "rb") as f:
undecodable = f.read()
- self.assertIn(b"?\xcb!", undecodable)
+ assert b"?\xcb!" in undecodable
cov.html_report()
html_report = self.get_html_report_content("sub/not_ascii.py")
expected = "# Isn't this great?�!"
- self.assertIn(expected, html_report)
+ assert expected in html_report
def test_formfeeds(self):
# https://github.com/nedbat/coveragepy/issues/360
@@ -444,7 +428,7 @@ def test_formfeeds(self):
cov.html_report()
formfeed_html = self.get_html_report_content("formfeed.py")
- self.assertIn("line_two", formfeed_html)
+ assert "line_two" in formfeed_html
class HtmlTest(HtmlTestHelpers, CoverageTest):
@@ -462,7 +446,7 @@ def test_missing_source_file_incorrect_message(self):
missing_file = os.path.join(self.temp_dir, "sub", "another.py")
missing_file = os.path.realpath(missing_file)
msg = "(?i)No source for code: '%s'" % re.escape(missing_file)
- with self.assertRaisesRegex(NoSource, msg):
+ with pytest.raises(NoSource, match=msg):
cov.html_report()
def test_extensionless_file_collides_with_extension(self):
@@ -538,7 +522,7 @@ def normal():
normal()
""")
res = self.run_coverage(covargs=dict(source="."), htmlargs=dict(skip_covered=True))
- self.assertEqual(res, 100.0)
+ assert res == 100.0
self.assert_doesnt_exist("htmlcov/main_file_py.html")
def make_init_and_main(self):
@@ -588,7 +572,7 @@ def test_copying_static_files_from_system(self):
with open("htmlcov/jquery.min.js") as f:
jquery = f.read()
- self.assertEqual(jquery, "Not Really JQuery!")
+ assert jquery == "Not Really JQuery!"
def test_copying_static_files_from_system_in_dir(self):
# Make a new place for static files.
@@ -611,7 +595,7 @@ def test_copying_static_files_from_system_in_dir(self):
the_file = os.path.basename(fpath)
with open(os.path.join("htmlcov", the_file)) as f:
contents = f.read()
- self.assertEqual(contents, "Not real.")
+ assert contents == "Not real."
def test_cant_find_static_files(self):
# Make the path point to useless places.
@@ -621,7 +605,7 @@ def test_cant_find_static_files(self):
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
msg = "Couldn't find static file u?'.*'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
cov.html_report()
def filepath_to_regex(path):
@@ -1094,10 +1078,9 @@ def html_data_from_cov(self, cov, morf):
"""Get HTML report data from a `Coverage` object for a morf."""
with self.assert_warnings(cov, []):
datagen = coverage.html.HtmlDataGeneration(cov)
- for fr, analysis in get_analysis_to_report(cov, [morf]):
- # This will only loop once, so it's fine to return inside the loop.
- file_data = datagen.data_for_file(fr, analysis)
- return file_data
+ fr, analysis = next(get_analysis_to_report(cov, [morf]))
+ file_data = datagen.data_for_file(fr, analysis)
+ return file_data
SOURCE = """\
def helper(lineno):
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 2f6fbe7c1..dad542acf 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -24,36 +24,36 @@ def test_string_hashing(self):
h2.update("Goodbye!")
h3 = Hasher()
h3.update("Hello, world!")
- self.assertNotEqual(h1.hexdigest(), h2.hexdigest())
- self.assertEqual(h1.hexdigest(), h3.hexdigest())
+ assert h1.hexdigest() != h2.hexdigest()
+ assert h1.hexdigest() == h3.hexdigest()
def test_bytes_hashing(self):
h1 = Hasher()
h1.update(b"Hello, world!")
h2 = Hasher()
h2.update(b"Goodbye!")
- self.assertNotEqual(h1.hexdigest(), h2.hexdigest())
+ assert h1.hexdigest() != h2.hexdigest()
def test_unicode_hashing(self):
h1 = Hasher()
h1.update(u"Hello, world! \N{SNOWMAN}")
h2 = Hasher()
h2.update(u"Goodbye!")
- self.assertNotEqual(h1.hexdigest(), h2.hexdigest())
+ assert h1.hexdigest() != h2.hexdigest()
def test_dict_hashing(self):
h1 = Hasher()
h1.update({'a': 17, 'b': 23})
h2 = Hasher()
h2.update({'b': 23, 'a': 17})
- self.assertEqual(h1.hexdigest(), h2.hexdigest())
+ assert 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())
+ assert h1.hexdigest() != h2.hexdigest()
class RemoveFileTest(CoverageTest):
@@ -72,20 +72,16 @@ def test_remove_actual_file(self):
def test_actual_errors(self):
# Errors can still happen.
# ". is a directory" on Unix, or "Access denied" on Windows
- with self.assertRaises(OSError):
+ with pytest.raises(OSError):
file_be_gone(".")
+@pytest.mark.skipif(not USE_CONTRACTS, reason="Contracts are disabled, can't test them")
class ContractTest(CoverageTest):
"""Tests of our contract decorators."""
run_in_temp_dir = False
- def setUp(self):
- super(ContractTest, self).setUp()
- if not USE_CONTRACTS:
- self.skipTest("Contracts are disabled")
-
def test_bytes(self):
@contract(text='bytes|None')
def need_bytes(text=None):
diff --git a/tests/test_numbits.py b/tests/test_numbits.py
index 232d48d3a..fc27a093e 100644
--- a/tests/test_numbits.py
+++ b/tests/test_numbits.py
@@ -47,7 +47,7 @@ def test_conversion(self, nums):
numbits = nums_to_numbits(nums)
good_numbits(numbits)
nums2 = numbits_to_nums(numbits)
- self.assertEqual(nums, set(nums2))
+ assert nums == set(nums2)
@given(line_number_sets, line_number_sets)
@settings(default_settings)
@@ -59,7 +59,7 @@ def test_union(self, nums1, nums2):
nbu = numbits_union(nb1, nb2)
good_numbits(nbu)
union = numbits_to_nums(nbu)
- self.assertEqual(nums1 | nums2, set(union))
+ assert nums1 | nums2 == set(union)
@given(line_number_sets, line_number_sets)
@settings(default_settings)
@@ -71,7 +71,7 @@ def test_intersection(self, nums1, nums2):
nbi = numbits_intersection(nb1, nb2)
good_numbits(nbi)
intersection = numbits_to_nums(nbi)
- self.assertEqual(nums1 & nums2, set(intersection))
+ assert nums1 & nums2 == set(intersection)
@given(line_number_sets, line_number_sets)
@settings(default_settings)
@@ -82,7 +82,7 @@ def test_any_intersection(self, nums1, nums2):
good_numbits(nb2)
inter = numbits_any_intersection(nb1, nb2)
expect = bool(nums1 & nums2)
- self.assertEqual(expect, bool(inter))
+ assert expect == bool(inter)
@given(line_numbers, line_number_sets)
@settings(default_settings)
@@ -91,7 +91,7 @@ def test_num_in_numbits(self, num, nums):
numbits = nums_to_numbits(nums)
good_numbits(numbits)
is_in = num_in_numbits(num, numbits)
- self.assertEqual(num in nums, is_in)
+ assert (num in nums) == is_in
class NumbitsSqliteFunctionTest(CoverageTest):
@@ -121,12 +121,12 @@ def test_numbits_union(self):
"(select numbits from data where id = 9)"
")"
)
+ expected = [
+ 7, 9, 14, 18, 21, 27, 28, 35, 36, 42, 45, 49,
+ 54, 56, 63, 70, 72, 77, 81, 84, 90, 91, 98, 99,
+ ]
answer = numbits_to_nums(list(res)[0][0])
- self.assertEqual(
- [7, 9, 14, 18, 21, 27, 28, 35, 36, 42, 45, 49,
- 54, 56, 63, 70, 72, 77, 81, 84, 90, 91, 98, 99],
- answer
- )
+ assert expected == answer
def test_numbits_intersection(self):
res = self.cursor.execute(
@@ -136,7 +136,7 @@ def test_numbits_intersection(self):
")"
)
answer = numbits_to_nums(list(res)[0][0])
- self.assertEqual([63], answer)
+ assert [63] == answer
def test_numbits_any_intersection(self):
res = self.cursor.execute(
@@ -144,20 +144,20 @@ def test_numbits_any_intersection(self):
(nums_to_numbits([1, 2, 3]), nums_to_numbits([3, 4, 5]))
)
answer = [any_inter for (any_inter,) in res]
- self.assertEqual([1], answer)
+ assert [1] == answer
res = self.cursor.execute(
"select numbits_any_intersection(?, ?)",
(nums_to_numbits([1, 2, 3]), nums_to_numbits([7, 8, 9]))
)
answer = [any_inter for (any_inter,) in res]
- self.assertEqual([0], answer)
+ assert [0] == answer
def test_num_in_numbits(self):
res = self.cursor.execute("select id, num_in_numbits(12, numbits) from data order by id")
answer = [is_in for (id, is_in) in res]
- self.assertEqual([1, 1, 1, 1, 0, 1, 0, 0, 0, 0], answer)
+ assert [1, 1, 1, 1, 0, 1, 0, 0, 0, 0] == answer
def test_numbits_to_nums(self):
res = self.cursor.execute("select numbits_to_nums(?)", [nums_to_numbits([1, 2, 3])])
- self.assertEqual([1, 2, 3], json.loads(res.fetchone()[0]))
+ assert [1, 2, 3] == json.loads(res.fetchone()[0])
diff --git a/tests/test_oddball.py b/tests/test_oddball.py
index 17f696477..b73078877 100644
--- a/tests/test_oddball.py
+++ b/tests/test_oddball.py
@@ -80,7 +80,7 @@ def recur(n):
def test_long_recursion(self):
# We can't finish a very deep recursion, but we don't crash.
- with self.assertRaises(RuntimeError):
+ with pytest.raises(RuntimeError):
self.check_coverage("""\
def recur(n):
if n == 0:
@@ -125,16 +125,14 @@ def recur(n):
expected_missing += [9, 10, 11]
_, statements, missing, _ = cov.analysis("recur.py")
- self.assertEqual(statements, [1, 2, 3, 5, 7, 8, 9, 10, 11])
- self.assertEqual(expected_missing, missing)
+ assert statements == [1, 2, 3, 5, 7, 8, 9, 10, 11]
+ assert expected_missing == missing
# Get a warning about the stackoverflow effect on the tracing function.
if pytrace: # pragma: no metacov
- self.assertEqual(cov._warnings,
- ["Trace function changed, measurement is likely wrong: None"]
- )
+ assert cov._warnings == ["Trace function changed, measurement is likely wrong: None"]
else:
- self.assertEqual(cov._warnings, [])
+ assert cov._warnings == []
class MemoryLeakTest(CoverageTest):
@@ -147,10 +145,8 @@ class MemoryLeakTest(CoverageTest):
"""
@flaky
+ @pytest.mark.skipif(env.JYTHON, reason="Don't bother on Jython")
def test_for_leaks(self):
- if env.JYTHON:
- self.skipTest("Don't bother on Jython")
-
# Our original bad memory leak only happened on line numbers > 255, so
# make a code object with more lines than that. Ugly string mumbo
# jumbo to get 300 blank lines at the beginning..
@@ -196,11 +192,10 @@ def once(x): # line 301
class MemoryFumblingTest(CoverageTest):
"""Test that we properly manage the None refcount."""
+ @pytest.mark.skipif(not env.C_TRACER, reason="Only the C tracer has refcounting issues")
def test_dropping_none(self): # pragma: not covered
- if not env.C_TRACER:
- self.skipTest("Only the C tracer has refcounting issues")
# TODO: Mark this so it will only be run sometimes.
- self.skipTest("This is too expensive for now (30s)")
+ pytest.skip("This is too expensive for now (30s)")
# Start and stop coverage thousands of times to flush out bad
# reference counting, maybe.
self.make_file("the_code.py", """\
@@ -224,18 +219,16 @@ def f():
print("Final None refcount: %d" % (sys.getrefcount(None)))
""")
status, out = self.run_command_status("python main.py")
- self.assertEqual(status, 0)
- self.assertIn("Final None refcount", out)
- self.assertNotIn("Fatal", out)
+ assert status == 0
+ assert "Final None refcount" in out
+ assert "Fatal" not in out
+@pytest.mark.skipif(env.JYTHON, reason="Pyexpat isn't a problem on Jython")
class PyexpatTest(CoverageTest):
"""Pyexpat screws up tracing. Make sure we've counter-defended properly."""
def test_pyexpat(self):
- if env.JYTHON:
- self.skipTest("Pyexpat isn't a problem on Jython")
-
# pyexpat calls the trace function explicitly (inexplicably), and does
# it wrong for exceptions. Parsing a DOCTYPE for some reason throws
# an exception internally, and triggers its wrong behavior. This test
@@ -268,20 +261,18 @@ def foo():
self.start_import_stop(cov, "outer")
_, statements, missing, _ = cov.analysis("trydom.py")
- self.assertEqual(statements, [1, 3, 8, 9, 10, 11, 13])
- self.assertEqual(missing, [])
+ assert statements == [1, 3, 8, 9, 10, 11, 13]
+ assert missing == []
_, statements, missing, _ = cov.analysis("outer.py")
- self.assertEqual(statements, [101, 102])
- self.assertEqual(missing, [])
+ assert statements == [101, 102]
+ assert missing == []
# Make sure pyexpat isn't recorded as a source file.
# https://github.com/nedbat/coveragepy/issues/419
files = cov.get_data().measured_files()
- self.assertFalse(
- any(f.endswith("pyexpat.c") for f in files),
- "Pyexpat.c is in the measured files!: %r:" % (files,)
- )
+ msg = "Pyexpat.c is in the measured files!: %r:" % (files,)
+ assert not any(f.endswith("pyexpat.c") for f in files), msg
class ExceptionTest(CoverageTest):
@@ -393,7 +384,7 @@ def doit(calls):
for lines in lines_expected.values():
lines[:] = [l for l in lines if l not in invisible]
- self.assertEqual(clean_lines, lines_expected)
+ assert clean_lines == lines_expected
class DoctestTest(CoverageTest):
@@ -451,11 +442,11 @@ def swap_it():
cov = coverage.Coverage(source=["sample"])
self.start_import_stop(cov, "main")
- self.assertEqual(self.stdout(), "10\n7\n3\n5\n9\n12\n")
+ assert self.stdout() == "10\n7\n3\n5\n9\n12\n"
_, statements, missing, _ = cov.analysis("sample.py")
- self.assertEqual(statements, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
- self.assertEqual(missing, [])
+ assert statements == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+ assert missing == []
def test_setting_new_trace_function(self):
# https://github.com/nedbat/coveragepy/issues/436
@@ -488,23 +479,19 @@ def test_unsets_trace():
)
out = self.stdout().replace(self.last_module_name, "coverage_test")
-
- self.assertEqual(
- out,
- (
- "call: coverage_test.py @ 10\n"
- "line: coverage_test.py @ 11\n"
- "line: coverage_test.py @ 12\n"
- "return: coverage_test.py @ 12\n"
- ),
+ expected = (
+ "call: coverage_test.py @ 10\n"
+ "line: coverage_test.py @ 11\n"
+ "line: coverage_test.py @ 12\n"
+ "return: coverage_test.py @ 12\n"
)
+ assert expected == out
@pytest.mark.expensive
- def test_atexit_gettrace(self): # pragma: no metacov
+ @pytest.mark.skipif(env.METACOV, reason="Can't set trace functions during meta-coverage")
+ def test_atexit_gettrace(self):
# This is not a test of coverage at all, but of our understanding
# of this edge-case behavior in various Pythons.
- if env.METACOV:
- self.skipTest("Can't set trace functions during meta-coverage")
self.make_file("atexit_gettrace.py", """\
import atexit, sys
@@ -523,13 +510,13 @@ def show_trace_function():
# This will show what the trace function is at the end of the program.
""")
status, out = self.run_command_status("python atexit_gettrace.py")
- self.assertEqual(status, 0)
+ assert status == 0
if env.PYPY and env.PYPYVERSION >= (5, 4):
# Newer PyPy clears the trace function before atexit runs.
- self.assertEqual(out, "None\n")
+ assert out == "None\n"
else:
# Other Pythons leave the trace function in place.
- self.assertEqual(out, "trace_function\n")
+ assert out == "trace_function\n"
class ExecTest(CoverageTest):
@@ -557,16 +544,15 @@ def test_correct_filename(self):
self.start_import_stop(cov, "main")
_, statements, missing, _ = cov.analysis("main.py")
- self.assertEqual(statements, [1, 2, 3, 4, 35])
- self.assertEqual(missing, [])
+ assert statements == [1, 2, 3, 4, 35]
+ assert missing == []
_, statements, missing, _ = cov.analysis("to_exec.py")
- self.assertEqual(statements, [31])
- self.assertEqual(missing, [])
+ assert statements == [31]
+ assert missing == []
+ @pytest.mark.skipif(env.PY2, reason="Python 2 can't seem to compile the file.")
def test_unencodable_filename(self):
# https://github.com/nedbat/coveragepy/issues/891
- if env.PYVERSION < (3, 0):
- self.skipTest("Python 2 can't seem to compile the file.")
self.make_file("bug891.py", r"""exec(compile("pass", "\udcff.py", "exec"))""")
cov = coverage.Coverage()
self.start_import_stop(cov, "bug891")
@@ -609,4 +595,4 @@ def test_path_exists(mock_exists):
import py_compile
py_compile.compile("bug416a.py")
out = self.run_command("coverage run bug416.py")
- self.assertEqual(out, "in test\nbug416a.py\n23\n17\n")
+ assert out == "in test\nbug416a.py\n23\n17\n"
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 9d3f9f678..6edb6d1a0 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -5,6 +5,8 @@
import textwrap
+import pytest
+
from coverage import env
from coverage.misc import NotPython
from coverage.parser import PythonParser
@@ -40,9 +42,9 @@ def foo(self, a):
class Bar:
pass
""")
- self.assertEqual(parser.exit_counts(), {
+ assert parser.exit_counts() == {
2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1
- })
+ }
def test_generator_exit_counts(self):
# https://github.com/nedbat/coveragepy/issues/324
@@ -53,12 +55,12 @@ def gen(input):
list(gen([1,2,3]))
""")
- self.assertEqual(parser.exit_counts(), {
+ assert parser.exit_counts() == {
1:1, # def -> list
2:2, # for -> yield; for -> exit
3:2, # yield -> for; genexp exit
5:1, # list -> exit
- })
+ }
def test_try_except(self):
parser = self.parse_source("""\
@@ -72,9 +74,9 @@ def test_try_except(self):
a = 8
b = 9
""")
- self.assertEqual(parser.exit_counts(), {
+ assert parser.exit_counts() == {
1: 1, 2:1, 3:2, 4:1, 5:2, 6:1, 7:1, 8:1, 9:1
- })
+ }
def test_excluded_classes(self):
parser = self.parse_source("""\
@@ -86,9 +88,9 @@ def __init__(self):
class Bar:
pass
""")
- self.assertEqual(parser.exit_counts(), {
+ assert parser.exit_counts() == {
1:0, 2:1, 3:1
- })
+ }
def test_missing_branch_to_excluded_code(self):
parser = self.parse_source("""\
@@ -98,7 +100,7 @@ def test_missing_branch_to_excluded_code(self):
a = 4
b = 5
""")
- self.assertEqual(parser.exit_counts(), { 1:1, 2:1, 5:1 })
+ assert parser.exit_counts() == { 1:1, 2:1, 5:1 }
parser = self.parse_source("""\
def foo():
if fooey:
@@ -107,7 +109,7 @@ def foo():
a = 5
b = 6
""")
- self.assertEqual(parser.exit_counts(), { 1:1, 2:2, 3:1, 5:1, 6:1 })
+ assert parser.exit_counts() == { 1:1, 2:2, 3:1, 5:1, 6:1 }
parser = self.parse_source("""\
def foo():
if fooey:
@@ -116,14 +118,14 @@ def foo():
a = 5
b = 6
""")
- self.assertEqual(parser.exit_counts(), { 1:1, 2:1, 3:1, 6:1 })
+ assert parser.exit_counts() == { 1:1, 2:1, 3:1, 6:1 }
def test_indentation_error(self):
msg = (
"Couldn't parse '' as Python source: "
"'unindent does not match any outer indentation level' at line 3"
)
- with self.assertRaisesRegex(NotPython, msg):
+ with pytest.raises(NotPython, match=msg):
_ = self.parse_source("""\
0 spaces
2
@@ -132,7 +134,7 @@ def test_indentation_error(self):
def test_token_error(self):
msg = "Couldn't parse '' as Python source: 'EOF in multi-line string' at line 1"
- with self.assertRaisesRegex(NotPython, msg):
+ with pytest.raises(NotPython, match=msg):
_ = self.parse_source("""\
'''
""")
@@ -174,8 +176,8 @@ def func(x=25):
raw_statements = {3, 4, 5, 6, 8, 9, 10, 13, 15, 16, 17, 20, 22, 23, 25, 26}
if env.PYBEHAVIOR.trace_decorated_def:
raw_statements.update([11, 19])
- self.assertEqual(parser.raw_statements, raw_statements)
- self.assertEqual(parser.statements, {8})
+ assert parser.raw_statements == raw_statements
+ assert parser.statements == {8}
def test_class_decorator_pragmas(self):
parser = self.parse_source("""\
@@ -188,8 +190,8 @@ class Bar(object):
def __init__(self):
self.x = 8
""")
- self.assertEqual(parser.raw_statements, {1, 2, 3, 5, 6, 7, 8})
- self.assertEqual(parser.statements, {1, 2, 3})
+ assert parser.raw_statements == {1, 2, 3, 5, 6, 7, 8}
+ assert parser.statements == {1, 2, 3}
def test_empty_decorated_function(self):
parser = self.parse_source("""\
@@ -219,9 +221,9 @@ def bar(self):
expected_arcs.update(set(arcz_to_arcs("-46 6-4")))
expected_exits.update({6: 1})
- self.assertEqual(expected_statements, parser.statements)
- self.assertEqual(expected_arcs, parser.arcs())
- self.assertEqual(expected_exits, parser.exit_counts())
+ assert expected_statements == parser.statements
+ assert expected_arcs == parser.arcs()
+ assert expected_exits == parser.exit_counts()
class ParserMissingArcDescriptionTest(CoverageTest):
@@ -252,31 +254,24 @@ def func10():
thing(12)
more_stuff(13)
""")
- self.assertEqual(
- parser.missing_arc_description(1, 2),
- "line 1 didn't jump to line 2, because the condition on line 1 was never true"
- )
- self.assertEqual(
- parser.missing_arc_description(1, 3),
- "line 1 didn't jump to line 3, because the condition on line 1 was never false"
+ expected = "line 1 didn't jump to line 2, because the condition on line 1 was never true"
+ assert expected == parser.missing_arc_description(1, 2)
+ expected = "line 1 didn't jump to line 3, because the condition on line 1 was never false"
+ assert expected == parser.missing_arc_description(1, 3)
+ expected = (
+ "line 6 didn't return from function 'func5', " +
+ "because the loop on line 6 didn't complete"
)
- self.assertEqual(
- parser.missing_arc_description(6, -5),
- "line 6 didn't return from function 'func5', "
- "because the loop on line 6 didn't complete"
- )
- self.assertEqual(
- parser.missing_arc_description(6, 7),
- "line 6 didn't jump to line 7, because the loop on line 6 never started"
- )
- self.assertEqual(
- parser.missing_arc_description(11, 12),
- "line 11 didn't jump to line 12, because the condition on line 11 was never true"
- )
- self.assertEqual(
- parser.missing_arc_description(11, 13),
- "line 11 didn't jump to line 13, because the condition on line 11 was never false"
+ assert expected == parser.missing_arc_description(6, -5)
+ expected = "line 6 didn't jump to line 7, because the loop on line 6 never started"
+ assert expected == parser.missing_arc_description(6, 7)
+ expected = "line 11 didn't jump to line 12, because the condition on line 11 was never true"
+ assert expected == parser.missing_arc_description(11, 12)
+ expected = (
+ "line 11 didn't jump to line 13, " +
+ "because the condition on line 11 was never false"
)
+ assert expected == parser.missing_arc_description(11, 13)
def test_missing_arc_descriptions_for_small_callables(self):
parser = self.parse_text(u"""\
@@ -288,22 +283,14 @@ def test_missing_arc_descriptions_for_small_callables(self):
]
x = 7
""")
- self.assertEqual(
- parser.missing_arc_description(2, -2),
- "line 2 didn't finish the lambda on line 2"
- )
- self.assertEqual(
- parser.missing_arc_description(3, -3),
- "line 3 didn't finish the generator expression on line 3"
- )
- self.assertEqual(
- parser.missing_arc_description(4, -4),
- "line 4 didn't finish the dictionary comprehension on line 4"
- )
- self.assertEqual(
- parser.missing_arc_description(5, -5),
- "line 5 didn't finish the set comprehension on line 5"
- )
+ expected = "line 2 didn't finish the lambda on line 2"
+ assert expected == parser.missing_arc_description(2, -2)
+ expected = "line 3 didn't finish the generator expression on line 3"
+ assert expected == parser.missing_arc_description(3, -3)
+ expected = "line 4 didn't finish the dictionary comprehension on line 4"
+ assert expected == parser.missing_arc_description(4, -4)
+ expected = "line 5 didn't finish the set comprehension on line 5"
+ assert expected == parser.missing_arc_description(5, -5)
def test_missing_arc_descriptions_for_exceptions(self):
parser = self.parse_text(u"""\
@@ -314,14 +301,16 @@ def test_missing_arc_descriptions_for_exceptions(self):
except ValueError:
print("yikes")
""")
- self.assertEqual(
- parser.missing_arc_description(3, 4),
- "line 3 didn't jump to line 4, because the exception caught by line 3 didn't happen"
+ expected = (
+ "line 3 didn't jump to line 4, " +
+ "because the exception caught by line 3 didn't happen"
)
- self.assertEqual(
- parser.missing_arc_description(5, 6),
- "line 5 didn't jump to line 6, because the exception caught by line 5 didn't happen"
+ assert expected == parser.missing_arc_description(3, 4)
+ expected = (
+ "line 5 didn't jump to line 6, " +
+ "because the exception caught by line 5 didn't happen"
)
+ assert expected == parser.missing_arc_description(5, 6)
def test_missing_arc_descriptions_for_finally(self):
parser = self.parse_text(u"""\
@@ -346,56 +335,56 @@ def function():
that_thing(19)
""")
if env.PYBEHAVIOR.finally_jumps_back:
- self.assertEqual(
- parser.missing_arc_description(18, 5),
- "line 18 didn't jump to line 5, because the break on line 5 wasn't executed"
- )
- self.assertEqual(
- parser.missing_arc_description(5, 19),
- "line 5 didn't jump to line 19, because the break on line 5 wasn't executed"
+ expected = "line 18 didn't jump to line 5, because the break on line 5 wasn't executed"
+ assert expected == parser.missing_arc_description(18, 5)
+ expected = "line 5 didn't jump to line 19, because the break on line 5 wasn't executed"
+ assert expected == parser.missing_arc_description(5, 19)
+ expected = (
+ "line 18 didn't jump to line 10, " +
+ "because the continue on line 10 wasn't executed"
)
- self.assertEqual(
- parser.missing_arc_description(18, 10),
- "line 18 didn't jump to line 10, because the continue on line 10 wasn't executed"
+ assert expected == parser.missing_arc_description(18, 10)
+ expected = (
+ "line 10 didn't jump to line 2, " +
+ "because the continue on line 10 wasn't executed"
)
- self.assertEqual(
- parser.missing_arc_description(10, 2),
- "line 10 didn't jump to line 2, because the continue on line 10 wasn't executed"
+ assert expected == parser.missing_arc_description(10, 2)
+ expected = (
+ "line 18 didn't jump to line 14, " +
+ "because the return on line 14 wasn't executed"
)
- self.assertEqual(
- parser.missing_arc_description(18, 14),
- "line 18 didn't jump to line 14, because the return on line 14 wasn't executed"
+ assert expected == parser.missing_arc_description(18, 14)
+ expected = (
+ "line 14 didn't return from function 'function', " +
+ "because the return on line 14 wasn't executed"
)
- self.assertEqual(
- parser.missing_arc_description(14, -1),
- "line 14 didn't return from function 'function', "
- "because the return on line 14 wasn't executed"
- )
- self.assertEqual(
- parser.missing_arc_description(18, -1),
- "line 18 didn't except from function 'function', "
- "because the raise on line 16 wasn't executed"
+ assert expected == parser.missing_arc_description(14, -1)
+ expected = (
+ "line 18 didn't except from function 'function', " +
+ "because the raise on line 16 wasn't executed"
)
+ assert expected == parser.missing_arc_description(18, -1)
else:
- self.assertEqual(
- parser.missing_arc_description(18, 19),
- "line 18 didn't jump to line 19, because the break on line 5 wasn't executed"
+ expected = (
+ "line 18 didn't jump to line 19, " +
+ "because the break on line 5 wasn't executed"
)
- self.assertEqual(
- parser.missing_arc_description(18, 2),
- "line 18 didn't jump to line 2, "
- "because the continue on line 10 wasn't executed"
- " or "
+ assert expected == parser.missing_arc_description(18, 19)
+ expected = (
+ "line 18 didn't jump to line 2, " +
+ "because the continue on line 10 wasn't executed" +
+ " or " +
"the continue on line 12 wasn't executed"
)
- self.assertEqual(
- parser.missing_arc_description(18, -1),
- "line 18 didn't except from function 'function', "
- "because the raise on line 16 wasn't executed"
- " or "
- "line 18 didn't return from function 'function', "
+ assert expected == parser.missing_arc_description(18, 2)
+ expected = (
+ "line 18 didn't except from function 'function', " +
+ "because the raise on line 16 wasn't executed" +
+ " or " +
+ "line 18 didn't return from function 'function', " +
"because the return on line 14 wasn't executed"
)
+ assert expected == parser.missing_arc_description(18, -1)
def test_missing_arc_descriptions_bug460(self):
parser = self.parse_text(u"""\
@@ -406,10 +395,7 @@ def test_missing_arc_descriptions_bug460(self):
}
x = 6
""")
- self.assertEqual(
- parser.missing_arc_description(2, -3),
- "line 3 didn't finish the lambda on line 3",
- )
+ assert parser.missing_arc_description(2, -3) == "line 3 didn't finish the lambda on line 3"
class ParserFileTest(CoverageTest):
@@ -440,18 +426,14 @@ class Bar:
fname = fname + ".py"
self.make_file(fname, text, newline=newline)
parser = self.parse_file(fname)
- self.assertEqual(
- parser.exit_counts(),
- counts,
- "Wrong for %r" % fname
- )
+ assert parser.exit_counts() == counts, "Wrong for %r" % fname
def test_encoding(self):
self.make_file("encoded.py", """\
coverage = "\xe7\xf6v\xear\xe3g\xe9"
""")
parser = self.parse_file("encoded.py")
- self.assertEqual(parser.exit_counts(), {1: 1})
+ assert parser.exit_counts() == {1: 1}
def test_missing_line_ending(self):
# Test that the set of statements is the same even if a final
@@ -466,7 +448,7 @@ def test_missing_line_ending(self):
""")
parser = self.parse_file("normal.py")
- self.assertEqual(parser.statements, {1})
+ assert parser.statements == {1}
self.make_file("abrupt.py", """\
out, err = subprocess.Popen(
@@ -476,7 +458,7 @@ def test_missing_line_ending(self):
# Double-check that some test helper wasn't being helpful.
with open("abrupt.py") as f:
- self.assertEqual(f.read()[-1], ")")
+ assert f.read()[-1] == ")"
parser = self.parse_file("abrupt.py")
- self.assertEqual(parser.statements, {1})
+ assert parser.statements == {1}
diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py
index 1256694a4..86b1fdbe4 100644
--- a/tests/test_phystokens.py
+++ b/tests/test_phystokens.py
@@ -7,6 +7,8 @@
import re
import textwrap
+import pytest
+
from coverage import env
from coverage.phystokens import source_token_lines, source_encoding
from coverage.phystokens import neuter_encoding_declaration, compile_unicode
@@ -67,23 +69,23 @@ def check_tokenization(self, source):
source = source.replace('\r\n', '\n')
source = re.sub(r"(?m)[ \t]+$", "", source)
tokenized = re.sub(r"(?m)[ \t]+$", "", tokenized)
- self.assertMultiLineEqual(source, tokenized)
+ assert source == tokenized
def check_file_tokenization(self, fname):
"""Use the contents of `fname` for `check_tokenization`."""
self.check_tokenization(get_python_source(fname))
def test_simple(self):
- self.assertEqual(list(source_token_lines(SIMPLE)), SIMPLE_TOKENS)
+ assert list(source_token_lines(SIMPLE)) == SIMPLE_TOKENS
self.check_tokenization(SIMPLE)
def test_missing_final_newline(self):
# We can tokenize source that is missing the final newline.
- self.assertEqual(list(source_token_lines(SIMPLE.rstrip())), SIMPLE_TOKENS)
+ assert list(source_token_lines(SIMPLE.rstrip())) == SIMPLE_TOKENS
def test_tab_indentation(self):
# Mixed tabs and spaces...
- self.assertEqual(list(source_token_lines(MIXED_WS)), MIXED_WS_TOKENS)
+ assert list(source_token_lines(MIXED_WS)) == MIXED_WS_TOKENS
def test_bug_822(self):
self.check_tokenization(BUG_822)
@@ -128,48 +130,42 @@ class SourceEncodingTest(CoverageTest):
def test_detect_source_encoding(self):
for _, source, expected in ENCODING_DECLARATION_SOURCES:
- self.assertEqual(
- source_encoding(source),
- expected,
- "Wrong encoding in %r" % source
- )
+ assert source_encoding(source) == expected, "Wrong encoding in %r" % source
+ # PyPy3 gets this case wrong. Not sure what I can do about it, so skip the test.
+ @pytest.mark.skipif(env.PYPY3, reason="PyPy3 is wrong about non-comment encoding. Skip it.")
def test_detect_source_encoding_not_in_comment(self):
- if env.PYPY3: # pragma: no metacov
- # PyPy3 gets this case wrong. Not sure what I can do about it,
- # so skip the test.
- self.skipTest("PyPy3 is wrong about non-comment encoding. Skip it.")
# Should not detect anything here
source = b'def parse(src, encoding=None):\n pass'
- self.assertEqual(source_encoding(source), DEF_ENCODING)
+ assert source_encoding(source) == DEF_ENCODING
def test_dont_detect_source_encoding_on_third_line(self):
# A coding declaration doesn't count on the third line.
source = b"\n\n# coding=cp850\n\n"
- self.assertEqual(source_encoding(source), DEF_ENCODING)
+ assert source_encoding(source) == DEF_ENCODING
def test_detect_source_encoding_of_empty_file(self):
# An important edge case.
- self.assertEqual(source_encoding(b""), DEF_ENCODING)
+ assert source_encoding(b"") == DEF_ENCODING
def test_bom(self):
# A BOM means utf-8.
source = b"\xEF\xBB\xBFtext = 'hello'\n"
- self.assertEqual(source_encoding(source), 'utf-8-sig')
+ assert source_encoding(source) == 'utf-8-sig'
def test_bom_with_encoding(self):
source = b"\xEF\xBB\xBF# coding: utf-8\ntext = 'hello'\n"
- self.assertEqual(source_encoding(source), 'utf-8-sig')
+ assert source_encoding(source) == 'utf-8-sig'
def test_bom_is_wrong(self):
# A BOM with an explicit non-utf8 encoding is an error.
source = b"\xEF\xBB\xBF# coding: cp850\n"
- with self.assertRaisesRegex(SyntaxError, "encoding problem: utf-8"):
+ with pytest.raises(SyntaxError, match="encoding problem: utf-8"):
source_encoding(source)
def test_unknown_encoding(self):
source = b"# coding: klingon\n"
- with self.assertRaisesRegex(SyntaxError, "unknown encoding: klingon"):
+ with pytest.raises(SyntaxError, match="unknown encoding: klingon"):
source_encoding(source)
@@ -186,21 +182,17 @@ def test_neuter_encoding_declaration(self):
# The neutered source should have the same number of lines.
source_lines = source.splitlines()
neutered_lines = neutered.splitlines()
- self.assertEqual(len(source_lines), len(neutered_lines))
+ assert len(source_lines) == len(neutered_lines)
# Only one of the lines should be different.
lines_different = sum(
int(nline != sline) for nline, sline in zip(neutered_lines, source_lines)
)
- self.assertEqual(lines_diff_expected, lines_different)
+ assert lines_diff_expected == lines_different
# The neutered source will be detected as having no encoding
# declaration.
- self.assertEqual(
- source_encoding(neutered),
- DEF_ENCODING,
- "Wrong encoding in %r" % neutered
- )
+ assert source_encoding(neutered) == DEF_ENCODING, "Wrong encoding in %r" % neutered
def test_two_encoding_declarations(self):
input_src = textwrap.dedent(u"""\
@@ -214,7 +206,7 @@ def test_two_encoding_declarations(self):
# -*- coding: utf-16 -*-
""")
output_src = neuter_encoding_declaration(input_src)
- self.assertEqual(expected_src, output_src)
+ assert expected_src == output_src
def test_one_encoding_declaration(self):
input_src = textwrap.dedent(u"""\
@@ -228,7 +220,7 @@ def test_one_encoding_declaration(self):
# -*- coding: ascii -*-
""")
output_src = neuter_encoding_declaration(input_src)
- self.assertEqual(expected_src, output_src)
+ assert expected_src == output_src
class Bug529Test(CoverageTest):
@@ -258,8 +250,8 @@ def test_two_strings_are_equal(self):
unittest.main()
''')
status, out = self.run_command_status("coverage run the_test.py")
- self.assertEqual(status, 0)
- self.assertIn("OK", out)
+ assert status == 0
+ assert "OK" in out
# If this test fails, the output will be super-confusing, because it
# has a failing unit test contained within the failing unit test.
@@ -276,7 +268,7 @@ def assert_compile_unicode(self, source):
code = compile_unicode(source, "", "exec")
globs = {}
exec(code, globs)
- self.assertEqual(globs['a'], 42)
+ assert globs['a'] == 42
def test_cp1252(self):
uni = u"""# coding: cp1252\n# \u201C curly \u201D\n"""
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index 813d370e3..aeffdb808 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -7,6 +7,8 @@
import os.path
from xml.etree import ElementTree
+import pytest
+
import coverage
from coverage import env
from coverage.backward import StringIO, import_local_file
@@ -53,10 +55,10 @@ def coverage_init(reg, options):
config = FakeConfig("plugin1", {})
plugins = Plugins.load_plugins([], config)
- self.assertFalse(plugins)
+ assert not plugins
plugins = Plugins.load_plugins(["plugin1"], config)
- self.assertTrue(plugins)
+ assert plugins
def test_importing_and_configuring(self):
self.make_file("plugin1.py", """\
@@ -74,10 +76,10 @@ def coverage_init(reg, options):
config = FakeConfig("plugin1", {'a': 'hello'})
plugins = list(Plugins.load_plugins(["plugin1"], config))
- self.assertEqual(len(plugins), 1)
- self.assertEqual(plugins[0].this_is, "me")
- self.assertEqual(plugins[0].options, {'a': 'hello'})
- self.assertEqual(config.asked_for, ['plugin1'])
+ assert len(plugins) == 1
+ assert plugins[0].this_is == "me"
+ assert plugins[0].options == {'a': 'hello'}
+ assert config.asked_for == ['plugin1']
def test_importing_and_configuring_more_than_one(self):
self.make_file("plugin1.py", """\
@@ -105,23 +107,23 @@ def coverage_init(reg, options):
config = FakeConfig("plugin1", {'a': 'hello'})
plugins = list(Plugins.load_plugins(["plugin1", "plugin2"], config))
- self.assertEqual(len(plugins), 2)
- self.assertEqual(plugins[0].this_is, "me")
- self.assertEqual(plugins[0].options, {'a': 'hello'})
- self.assertEqual(plugins[1].options, {})
- self.assertEqual(config.asked_for, ['plugin1', 'plugin2'])
+ assert len(plugins) == 2
+ assert plugins[0].this_is == "me"
+ assert plugins[0].options == {'a': 'hello'}
+ assert plugins[1].options == {}
+ assert config.asked_for == ['plugin1', 'plugin2']
# The order matters...
config = FakeConfig("plugin1", {'a': 'second'})
plugins = list(Plugins.load_plugins(["plugin2", "plugin1"], config))
- self.assertEqual(len(plugins), 2)
- self.assertEqual(plugins[0].options, {})
- self.assertEqual(plugins[1].this_is, "me")
- self.assertEqual(plugins[1].options, {'a': 'second'})
+ assert len(plugins) == 2
+ assert plugins[0].options == {}
+ assert plugins[1].this_is == "me"
+ assert plugins[1].options == {'a': 'second'}
def test_cant_import(self):
- with self.assertRaisesRegex(ImportError, "No module named '?plugin_not_there'?"):
+ with pytest.raises(ImportError, match="No module named '?plugin_not_there'?"):
_ = Plugins.load_plugins(["plugin_not_there"], None)
def test_plugin_must_define_coverage_init(self):
@@ -130,7 +132,7 @@ def test_plugin_must_define_coverage_init(self):
Nothing = 0
""")
msg_pat = "Plugin module 'no_plugin' didn't define a coverage_init function"
- with self.assertRaisesRegex(CoverageException, msg_pat):
+ with pytest.raises(CoverageException, match=msg_pat):
list(Plugins.load_plugins(["no_plugin"], None))
@@ -156,11 +158,11 @@ def coverage_init(reg, options):
cov.stop() # pragma: nested
with open("evidence.out") as f:
- self.assertEqual(f.read(), "we are here!")
+ assert f.read() == "we are here!"
def test_missing_plugin_raises_import_error(self):
# Prove that a missing plugin will raise an ImportError.
- with self.assertRaisesRegex(ImportError, "No module named '?does_not_exist_woijwoicweo'?"):
+ with pytest.raises(ImportError, match="No module named '?does_not_exist_woijwoicweo'?"):
cov = coverage.Coverage()
cov.set_option("run:plugins", ["does_not_exist_woijwoicweo"])
cov.start()
@@ -169,7 +171,7 @@ def test_missing_plugin_raises_import_error(self):
def test_bad_plugin_isnt_hidden(self):
# Prove that a plugin with an error in it will raise the error.
self.make_file("plugin_over_zero.py", "1/0")
- with self.assertRaises(ZeroDivisionError):
+ with pytest.raises(ZeroDivisionError):
cov = coverage.Coverage()
cov.set_option("run:plugins", ["plugin_over_zero"])
cov.start()
@@ -195,16 +197,16 @@ def coverage_init(reg, options):
out_lines = [line.strip() for line in debug_out.getvalue().splitlines()]
if env.C_TRACER:
- self.assertIn('plugins.file_tracers: plugin_sys_info.Plugin', out_lines)
+ assert 'plugins.file_tracers: plugin_sys_info.Plugin' in out_lines
else:
- self.assertIn('plugins.file_tracers: plugin_sys_info.Plugin (disabled)', out_lines)
- self.assertIn('plugins.configurers: -none-', out_lines)
+ assert 'plugins.file_tracers: plugin_sys_info.Plugin (disabled)' in out_lines
+ assert 'plugins.configurers: -none-' in out_lines
expected_end = [
"-- sys: plugin_sys_info.Plugin -------------------------------",
"hello: world",
"-- end -------------------------------------------------------",
]
- self.assertEqual(expected_end, out_lines[-len(expected_end):])
+ assert expected_end == out_lines[-len(expected_end):]
def test_plugin_with_no_sys_info(self):
self.make_file("plugin_no_sys_info.py", """\
@@ -224,13 +226,13 @@ def coverage_init(reg, options):
cov.stop() # pragma: nested
out_lines = [line.strip() for line in debug_out.getvalue().splitlines()]
- self.assertIn('plugins.file_tracers: -none-', out_lines)
- self.assertIn('plugins.configurers: plugin_no_sys_info.Plugin', out_lines)
+ assert 'plugins.file_tracers: -none-' in out_lines
+ assert 'plugins.configurers: plugin_no_sys_info.Plugin' in out_lines
expected_end = [
"-- sys: plugin_no_sys_info.Plugin ----------------------------",
"-- end -------------------------------------------------------",
]
- self.assertEqual(expected_end, out_lines[-len(expected_end):])
+ assert expected_end == out_lines[-len(expected_end):]
def test_local_files_are_importable(self):
self.make_file("importing_plugin.py", """\
@@ -249,17 +251,15 @@ def coverage_init(reg, options):
self.make_file("main_file.py", "print('MAIN')")
out = self.run_command("coverage run main_file.py")
- self.assertEqual(out, "MAIN\n")
+ assert out == "MAIN\n"
out = self.run_command("coverage html")
- self.assertEqual(out, "")
+ assert out == ""
+@pytest.mark.skipif(env.C_TRACER, reason="This test is only about PyTracer.")
class PluginWarningOnPyTracer(CoverageTest):
"""Test that we get a controlled exception with plugins on PyTracer."""
def test_exception_if_plugins_on_pytracer(self):
- if env.C_TRACER:
- self.skipTest("This test is only about PyTracer.")
-
self.make_file("simple.py", "a = 1")
cov = coverage.Coverage()
@@ -272,14 +272,10 @@ def test_exception_if_plugins_on_pytracer(self):
self.start_import_stop(cov, "simple")
+@pytest.mark.skipif(not env.C_TRACER, reason="Plugins are only supported with the C tracer.")
class FileTracerTest(CoverageTest):
"""Tests of plugins that implement file_tracer."""
- def setUp(self):
- if not env.C_TRACER:
- self.skipTest("Plugins are only supported with the C tracer.")
- super(FileTracerTest, self).setUp()
-
class GoodFileTracerTest(FileTracerTest):
"""Tests of file tracer plugin happy paths."""
@@ -304,11 +300,11 @@ def test_plugin1(self):
self.start_import_stop(cov, "simple")
_, statements, missing, _ = cov.analysis("simple.py")
- self.assertEqual(statements, [1, 2, 3])
- self.assertEqual(missing, [])
+ assert statements == [1, 2, 3]
+ assert missing == []
zzfile = os.path.abspath(os.path.join("/src", "try_ABC.zz"))
_, statements, _, _ = cov.analysis(zzfile)
- self.assertEqual(statements, [105, 106, 107, 205, 206, 207])
+ assert statements == [105, 106, 107, 205, 206, 207]
def make_render_and_caller(self):
"""Make the render.py and caller.py files we need."""
@@ -370,21 +366,21 @@ def test_plugin2(self):
# have 7 lines in it. If render() was called with line number 4,
# then the plugin will claim that lines 4 and 5 were executed.
_, 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", line_counts(cov.get_data()))
+ assert statements == [1, 2, 3, 4, 5, 6, 7]
+ assert missing == [1, 2, 3, 6, 7]
+ assert "foo_7.html" in 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", line_counts(cov.get_data()))
+ assert statements == [1, 2, 3, 4]
+ assert missing == [1, 4]
+ assert "bar_4.html" in line_counts(cov.get_data())
- self.assertNotIn("quux_5.html", line_counts(cov.get_data()))
+ assert "quux_5.html" not in 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", line_counts(cov.get_data()))
+ assert statements == [1, 2, 3]
+ assert missing == [1]
+ assert "uni_3.html" in line_counts(cov.get_data())
def test_plugin2_with_branch(self):
self.make_render_and_caller()
@@ -400,12 +396,12 @@ def test_plugin2_with_branch(self):
# have 7 lines in it. If render() was called with line number 4,
# then the plugin will claim that lines 4 and 5 were executed.
analysis = cov._analyze("foo_7.html")
- self.assertEqual(analysis.statements, {1, 2, 3, 4, 5, 6, 7})
+ assert analysis.statements == {1, 2, 3, 4, 5, 6, 7}
# Plugins don't do branch coverage yet.
- self.assertEqual(analysis.has_arcs(), True)
- self.assertEqual(analysis.arc_possibilities(), [])
+ assert analysis.has_arcs() is True
+ assert analysis.arc_possibilities() == []
- self.assertEqual(analysis.missing, {1, 2, 3, 6, 7})
+ assert analysis.missing == {1, 2, 3, 6, 7}
def test_plugin2_with_text_report(self):
self.make_render_and_caller()
@@ -426,8 +422,8 @@ def test_plugin2_with_text_report(self):
'--------------------------------------------------------',
'TOTAL 11 7 0 0 36%',
]
- self.assertEqual(expected, report)
- self.assertAlmostEqual(total, 36.36, places=2)
+ assert expected == report
+ assert round(abs(total-36.36), 2) == 0
def test_plugin2_with_html_report(self):
self.make_render_and_caller()
@@ -438,7 +434,7 @@ def test_plugin2_with_html_report(self):
self.start_import_stop(cov, "caller")
total = cov.html_report(include=["*.html"], omit=["uni*.html"])
- self.assertAlmostEqual(total, 36.36, places=2)
+ assert round(abs(total-36.36), 2) == 0
self.assert_exists("htmlcov/index.html")
self.assert_exists("htmlcov/bar_4_html.html")
@@ -453,7 +449,7 @@ def test_plugin2_with_xml_report(self):
self.start_import_stop(cov, "caller")
total = cov.xml_report(include=["*.html"], omit=["uni*.html"])
- self.assertAlmostEqual(total, 36.36, places=2)
+ assert round(abs(total-36.36), 2) == 0
dom = ElementTree.parse("coverage.xml")
classes = {}
@@ -525,8 +521,8 @@ def coverage_init(reg, options):
'-----------------------------------------------',
'TOTAL 6 3 50%',
]
- self.assertEqual(expected, report)
- self.assertEqual(total, 50)
+ assert expected == report
+ assert total == 50
def test_find_unexecuted(self):
self.make_file("unexecuted_plugin.py", """\
@@ -567,17 +563,17 @@ def coverage_init(reg, options):
# The file we executed claims to have run line 999.
_, statements, missing, _ = cov.analysis("foo.py")
- self.assertEqual(statements, [99, 999, 9999])
- self.assertEqual(missing, [99, 9999])
+ assert statements == [99, 999, 9999]
+ assert missing == [99, 9999]
# The completely missing file is in the results.
_, statements, missing, _ = cov.analysis("chimera.py")
- self.assertEqual(statements, [99, 999, 9999])
- self.assertEqual(missing, [99, 999, 9999])
+ assert statements == [99, 999, 9999]
+ assert missing == [99, 999, 9999]
# But completely new filenames are not in the results.
- self.assertEqual(len(cov.get_data().measured_files()), 3)
- with self.assertRaises(CoverageException):
+ assert len(cov.get_data().measured_files()) == 3
+ with pytest.raises(CoverageException):
cov.analysis("fictional.py")
@@ -641,7 +637,7 @@ def run_bad_plugin(self, module_name, plugin_name, our_error=True, excmsg=None,
if our_error:
errors = stderr.count("# Oh noes!")
# The exception we're causing should only appear once.
- self.assertEqual(errors, 1)
+ assert errors == 1
# There should be a warning explaining what's happening, but only one.
# The message can be in two forms:
@@ -650,12 +646,12 @@ def run_bad_plugin(self, module_name, plugin_name, our_error=True, excmsg=None,
# Disabling plug-in '...' due to an exception:
msg = "Disabling plug-in '%s.%s' due to " % (module_name, plugin_name)
warnings = stderr.count(msg)
- self.assertEqual(warnings, 1)
+ assert warnings == 1
if excmsg:
- self.assertIn(excmsg, stderr)
+ assert excmsg in stderr
if excmsgs:
- self.assertTrue(any(em in stderr for em in excmsgs), "expected one of %r" % excmsgs)
+ assert any(em in stderr for em in excmsgs), "expected one of %r" % excmsgs
def test_file_tracer_has_no_file_tracer_method(self):
self.make_file("bad_plugin.py", """\
@@ -703,7 +699,7 @@ def coverage_init(reg, options):
""")
cov = self.run_plugin("bad_plugin")
expected_msg = "Plugin 'bad_plugin.Plugin' needs to implement file_reporter()"
- with self.assertRaisesRegex(NotImplementedError, expected_msg):
+ with pytest.raises(NotImplementedError, match=expected_msg):
cov.report()
def test_file_tracer_fails(self):
@@ -942,8 +938,8 @@ def test_configurer_plugin(self):
cov.start()
cov.stop() # pragma: nested
excluded = cov.get_option("report:exclude_lines")
- self.assertIn("pragma: custom", excluded)
- self.assertIn("pragma: or whatever", excluded)
+ assert "pragma: custom" in excluded
+ assert "pragma: or whatever" in excluded
class DynamicContextPluginTest(CoverageTest):
@@ -1048,16 +1044,14 @@ def test_plugin_standalone(self):
# Labeled coverage is collected
data = cov.get_data()
filenames = self.get_measured_filenames(data)
- self.assertEqual(
- ['', 'doctest:HTML_TAG', 'test:HTML_TAG', 'test:RENDERERS'],
- sorted(data.measured_contexts()),
- )
+ expected = ['', 'doctest:HTML_TAG', 'test:HTML_TAG', 'test:RENDERERS']
+ assert expected == sorted(data.measured_contexts())
data.set_query_context("doctest:HTML_TAG")
- self.assertEqual([2], data.lines(filenames['rendering.py']))
+ assert [2] == data.lines(filenames['rendering.py'])
data.set_query_context("test:HTML_TAG")
- self.assertEqual([2], data.lines(filenames['rendering.py']))
+ assert [2] == data.lines(filenames['rendering.py'])
data.set_query_context("test:RENDERERS")
- self.assertEqual([2, 5, 8, 11], sorted(data.lines(filenames['rendering.py'])))
+ assert [2, 5, 8, 11] == sorted(data.lines(filenames['rendering.py']))
def test_static_context(self):
self.make_plugin_capitalized_testnames('plugin_tests.py')
@@ -1078,7 +1072,7 @@ def test_static_context(self):
'mytests|test:HTML_TAG',
'mytests|test:RENDERERS',
]
- self.assertEqual(expected, sorted(data.measured_contexts()))
+ assert expected == sorted(data.measured_contexts())
def test_plugin_with_test_function(self):
self.make_plugin_capitalized_testnames('plugin_tests.py')
@@ -1103,11 +1097,11 @@ def test_plugin_with_test_function(self):
'testsuite.test_html_tag',
'testsuite.test_renderers',
]
- self.assertEqual(expected, sorted(data.measured_contexts()))
+ assert expected == sorted(data.measured_contexts())
def assert_context_lines(context, lines):
data.set_query_context(context)
- self.assertEqual(lines, sorted(data.lines(filenames['rendering.py'])))
+ assert lines == sorted(data.lines(filenames['rendering.py']))
assert_context_lines("doctest:HTML_TAG", [2])
assert_context_lines("testsuite.test_html_tag", [2])
@@ -1141,11 +1135,11 @@ def test_multiple_plugins(self):
'test:HTML_TAG',
'test:RENDERERS',
]
- self.assertEqual(expected, sorted(data.measured_contexts()))
+ assert expected == sorted(data.measured_contexts())
def assert_context_lines(context, lines):
data.set_query_context(context)
- self.assertEqual(lines, sorted(data.lines(filenames['rendering.py'])))
+ assert lines == sorted(data.lines(filenames['rendering.py']))
assert_context_lines("test:HTML_TAG", [2])
assert_context_lines("test:RENDERERS", [2, 5, 8, 11])
diff --git a/tests/test_process.py b/tests/test_process.py
index e48861568..9f07b9ccb 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -52,7 +52,7 @@ def test_environment(self):
self.assert_doesnt_exist(".coverage")
out = self.run_command("coverage run mycode.py")
self.assert_exists(".coverage")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
def make_b_or_c_py(self):
"""Create b_or_c.py, used in a few of these tests."""
@@ -74,12 +74,12 @@ def make_b_or_c_py(self):
def test_combine_parallel_data(self):
self.make_b_or_c_py()
out = self.run_command("coverage run -p b_or_c.py b")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
self.assert_file_count(".coverage.*", 1)
out = self.run_command("coverage run -p b_or_c.py c")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
# After two -p runs, there should be two .coverage.machine.123 files.
@@ -96,28 +96,28 @@ def test_combine_parallel_data(self):
# executed.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 8)
+ assert line_counts(data)['b_or_c.py'] == 8
# Running combine again should fail, because there are no parallel data
# files to combine.
status, out = self.run_command_status("coverage combine")
- self.assertEqual(status, 1)
- self.assertEqual(out, "No data to combine\n")
+ assert status == 1
+ assert out == "No data to combine\n"
# And the originally combined data is still there.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 8)
+ assert 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')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
self.assert_file_count(".coverage.*", 1)
out = self.run_command("coverage run -p b_or_c.py c")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
# After two -p runs, there should be two .coverage.machine.123 files.
@@ -134,7 +134,7 @@ def test_combine_parallel_data_with_a_corrupt_file(self):
r"Coverage.py warning: Couldn't use data file '.*\.coverage\.bad': "
r"file (is encrypted or )?is not a database"
)
- self.assertRegex(out, warning_regex)
+ assert re.search(warning_regex, out)
# After combining, those two should be the only data files.
self.assert_file_count(".coverage.*", 1)
@@ -143,13 +143,13 @@ def test_combine_parallel_data_with_a_corrupt_file(self):
# executed.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 8)
+ assert line_counts(data)['b_or_c.py'] == 8
def test_combine_no_usable_files(self):
# https://github.com/nedbat/coveragepy/issues/629
self.make_b_or_c_py()
out = self.run_command("coverage run b_or_c.py b")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_exists(".coverage")
self.assert_file_count(".coverage.*", 0)
@@ -159,7 +159,7 @@ def test_combine_no_usable_files(self):
# Combine the parallel coverage data files into .coverage, but nothing is readable.
status, out = self.run_command_status("coverage combine")
- self.assertEqual(status, 1)
+ assert status == 1
for n in "12":
self.assert_exists(".coverage.bad{}".format(n))
@@ -168,8 +168,8 @@ def test_combine_no_usable_files(self):
r"file (is encrypted or )?is not a database"
.format(n)
)
- self.assertRegex(out, warning_regex)
- self.assertRegex(out, r"No usable data files")
+ assert re.search(warning_regex, out)
+ assert re.search(r"No usable data files", out)
# After combining, we should have a main file and two parallel files.
self.assert_exists(".coverage")
@@ -179,13 +179,13 @@ def test_combine_no_usable_files(self):
# executed (we only did b, not c).
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 6)
+ assert line_counts(data)['b_or_c.py'] == 6
def test_combine_parallel_data_in_two_steps(self):
self.make_b_or_c_py()
out = self.run_command("coverage run -p b_or_c.py b")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
self.assert_file_count(".coverage.*", 1)
@@ -195,7 +195,7 @@ def test_combine_parallel_data_in_two_steps(self):
self.assert_file_count(".coverage.*", 0)
out = self.run_command("coverage run -p b_or_c.py c")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_exists(".coverage")
self.assert_file_count(".coverage.*", 1)
@@ -210,13 +210,13 @@ def test_combine_parallel_data_in_two_steps(self):
# executed.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 8)
+ assert 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')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
self.assert_file_count(".coverage.*", 1)
@@ -226,7 +226,7 @@ def test_combine_parallel_data_no_append(self):
self.assert_file_count(".coverage.*", 0)
out = self.run_command("coverage run -p b_or_c.py c")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_exists(".coverage")
self.assert_file_count(".coverage.*", 1)
@@ -242,18 +242,40 @@ def test_combine_parallel_data_no_append(self):
# 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)
+ assert line_counts(data)['b_or_c.py'] == 7
+
+ def test_combine_parallel_data_keep(self):
+ self.make_b_or_c_py()
+ out = self.run_command("coverage run -p b_or_c.py b")
+ assert out == 'done\n'
+ self.assert_doesnt_exist(".coverage")
+ self.assert_file_count(".coverage.*", 1)
+
+ out = self.run_command("coverage run -p b_or_c.py c")
+ assert out == 'done\n'
+ self.assert_doesnt_exist(".coverage")
+
+ # After two -p runs, there should be two .coverage.machine.123 files.
+ self.assert_file_count(".coverage.*", 2)
+
+ # Combine the parallel coverage data files into .coverage with the keep flag.
+ self.run_command("coverage combine --keep")
+
+ # After combining, the .coverage file & the original combined file should still be there.
+ self.assert_exists(".coverage")
+ self.assert_file_count(".coverage.*", 2)
+
def test_append_data(self):
self.make_b_or_c_py()
out = self.run_command("coverage run b_or_c.py b")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_exists(".coverage")
self.assert_file_count(".coverage.*", 0)
out = self.run_command("coverage run --append b_or_c.py c")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_exists(".coverage")
self.assert_file_count(".coverage.*", 0)
@@ -261,7 +283,7 @@ def test_append_data(self):
# executed.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 8)
+ assert line_counts(data)['b_or_c.py'] == 8
def test_append_data_with_different_file(self):
self.make_b_or_c_py()
@@ -272,12 +294,12 @@ def test_append_data_with_different_file(self):
""")
out = self.run_command("coverage run b_or_c.py b")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
self.assert_exists(".mycovdata")
out = self.run_command("coverage run --append b_or_c.py c")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
self.assert_exists(".mycovdata")
@@ -285,13 +307,13 @@ def test_append_data_with_different_file(self):
# executed.
data = coverage.CoverageData(".mycovdata")
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 8)
+ assert line_counts(data)['b_or_c.py'] == 8
def test_append_can_create_a_data_file(self):
self.make_b_or_c_py()
out = self.run_command("coverage run --append b_or_c.py b")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_exists(".coverage")
self.assert_file_count(".coverage.*", 0)
@@ -299,7 +321,7 @@ def test_append_can_create_a_data_file(self):
# executed.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 6)
+ assert line_counts(data)['b_or_c.py'] == 6
def test_combine_with_rc(self):
self.make_b_or_c_py()
@@ -311,11 +333,11 @@ def test_combine_with_rc(self):
""")
out = self.run_command("coverage run b_or_c.py b")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
out = self.run_command("coverage run b_or_c.py c")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
self.assert_doesnt_exist(".coverage")
# After two runs, there should be two .coverage.machine.123 files.
@@ -333,17 +355,17 @@ def test_combine_with_rc(self):
# executed.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['b_or_c.py'], 8)
+ assert 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("""\
+ assert out == textwrap.dedent("""\
Name Stmts Miss Cover
-------------------------------
b_or_c.py 8 0 100%
-------------------------------
TOTAL 8 0 100%
- """))
+ """)
def test_combine_with_aliases(self):
self.make_file("d1/x.py", """\
@@ -374,9 +396,9 @@ def test_combine_with_aliases(self):
""")
out = self.run_command("coverage run " + os.path.normpath("d1/x.py"))
- self.assertEqual(out, '1 2\n')
+ assert out == '1 2\n'
out = self.run_command("coverage run " + os.path.normpath("d2/x.py"))
- self.assertEqual(out, '4 5\n')
+ assert out == '4 5\n'
self.assert_file_count(".coverage.*", 2)
@@ -391,11 +413,11 @@ def test_combine_with_aliases(self):
data = coverage.CoverageData()
data.read()
summary = line_counts(data, fullpath=True)
- self.assertEqual(len(summary), 1)
+ assert len(summary) == 1
actual = abs_file(list(summary.keys())[0])
expected = abs_file('src/x.py')
- self.assertEqual(expected, actual)
- self.assertEqual(list(summary.values())[0], 6)
+ assert expected == actual
+ assert list(summary.values())[0] == 6
def test_erase_parallel(self):
self.make_file(".coveragerc", """\
@@ -423,8 +445,8 @@ def test_missing_source_file(self):
self.run_command("coverage run fleeting.py")
os.remove("fleeting.py")
out = self.run_command("coverage html -d htmlcov")
- self.assertRegex(out, "No source for code: '.*fleeting.py'")
- self.assertNotIn("Traceback", out)
+ assert re.search("No source for code: '.*fleeting.py'", out)
+ assert "Traceback" not in out
# It happens that the code paths are different for *.py and other
# files, so try again with no extension.
@@ -435,16 +457,16 @@ def test_missing_source_file(self):
self.run_command("coverage run fleeting")
os.remove("fleeting")
status, out = self.run_command_status("coverage html -d htmlcov")
- self.assertRegex(out, "No source for code: '.*fleeting'")
- self.assertNotIn("Traceback", out)
- self.assertEqual(status, 1)
+ assert re.search("No source for code: '.*fleeting'", out)
+ assert "Traceback" not in out
+ assert status == 1
def test_running_missing_file(self):
status, out = self.run_command_status("coverage run xyzzy.py")
- self.assertRegex(out, "No file to run: .*xyzzy.py")
- self.assertNotIn("raceback", out)
- self.assertNotIn("rror", out)
- self.assertEqual(status, 1)
+ assert re.search("No file to run: .*xyzzy.py", out)
+ assert "raceback" not in out
+ assert "rror" not in out
+ assert status == 1
def test_code_throws(self):
self.make_file("throw.py", """\
@@ -464,14 +486,14 @@ def f2():
if env.PYPY:
# Pypy has an extra frame in the traceback for some reason
out2 = re_lines(out2, "toplevel", match=False)
- self.assertMultiLineEqual(out, out2)
+ assert out == out2
# But also make sure that the output is what we expect.
path = python_reported_file('throw.py')
msg = 'File "{}", line 5,? in f2'.format(re.escape(path))
- self.assertRegex(out, msg)
- self.assertIn('raise Exception("hey!")', out)
- self.assertEqual(status, 1)
+ assert re.search(msg, out)
+ assert 'raise Exception("hey!")' in out
+ assert status == 1
def test_code_exits(self):
self.make_file("exit.py", """\
@@ -490,10 +512,10 @@ def f2():
# same output. No traceback.
status, out = self.run_command_status("coverage run exit.py")
status2, out2 = self.run_command_status("python exit.py")
- self.assertMultiLineEqual(out, out2)
- self.assertMultiLineEqual(out, "about to exit..\n")
- self.assertEqual(status, status2)
- self.assertEqual(status, 17)
+ assert out == out2
+ assert out == "about to exit..\n"
+ assert status == status2
+ assert status == 17
def test_code_exits_no_arg(self):
self.make_file("exit_none.py", """\
@@ -506,15 +528,13 @@ def f1():
""")
status, out = self.run_command_status("coverage run exit_none.py")
status2, out2 = self.run_command_status("python exit_none.py")
- self.assertMultiLineEqual(out, out2)
- self.assertMultiLineEqual(out, "about to exit quietly..\n")
- self.assertEqual(status, status2)
- self.assertEqual(status, 0)
+ assert out == out2
+ assert out == "about to exit quietly..\n"
+ assert status == status2
+ assert status == 0
+ @pytest.mark.skipif(not hasattr(os, "fork"), reason="Can't test os.fork, it doesn't exist.")
def test_fork(self):
- if not hasattr(os, 'fork'):
- self.skipTest("Can't test os.fork since it doesn't exist.")
-
self.make_file("fork.py", """\
import os
@@ -533,7 +553,7 @@ def main():
""")
out = self.run_command("coverage run -p fork.py")
- self.assertEqual(out, 'Child!\n')
+ assert out == 'Child!\n'
self.assert_doesnt_exist(".coverage")
# After running the forking program, there should be two
@@ -544,7 +564,7 @@ def main():
# the file name.
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,))
+ assert len(nums) == 2, "Same random: %s" % (data_files,)
# Combine the parallel coverage data files into .coverage .
self.run_command("coverage combine")
@@ -555,7 +575,7 @@ def main():
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['fork.py'], 9)
+ assert line_counts(data)['fork.py'] == 9
def test_warnings_during_reporting(self):
# While fixing issue #224, the warnings were being printed far too
@@ -576,7 +596,7 @@ def test_warnings_during_reporting(self):
self.run_command("coverage run hello.py")
out = self.run_command("coverage html")
- self.assertEqual(out.count("Module xyzzy was never imported."), 0)
+ assert out.count("Module xyzzy was never imported.") == 0
def test_warns_if_never_run(self):
# Note: the name of the function can't have "warning" in it, or the
@@ -584,22 +604,21 @@ def test_warns_if_never_run(self):
# will fail.
out = self.run_command("coverage run i_dont_exist.py")
path = python_reported_file('i_dont_exist.py')
- self.assertIn("No file to run: '{}'".format(path), out)
- self.assertNotIn("warning", out)
- self.assertNotIn("Exception", out)
+ assert "No file to run: '{}'".format(path) in out
+ assert "warning" not in out
+ assert "Exception" not in out
out = self.run_command("coverage run -m no_such_module")
- self.assertTrue(
+ assert (
("No module named no_such_module" in out) or
("No module named 'no_such_module'" in out)
- )
- self.assertNotIn("warning", out)
- self.assertNotIn("Exception", out)
+ )
+ assert "warning" not in out
+ assert "Exception" not in out
+ @pytest.mark.skipif(env.METACOV, reason="Can't test tracers changing during metacoverage")
def test_warnings_trace_function_changed_with_threads(self):
# https://github.com/nedbat/coveragepy/issues/164
- if env.METACOV:
- self.skipTest("Can't test tracers changing during metacoverage")
self.make_file("bug164.py", """\
import threading
@@ -615,8 +634,8 @@ def run(self):
""")
out = self.run_command("coverage run --timid bug164.py")
- self.assertIn("Hello\n", out)
- self.assertNotIn("warning", out)
+ assert "Hello\n" in out
+ assert "warning" not in out
def test_warning_trace_function_changed(self):
self.make_file("settrace.py", """\
@@ -626,11 +645,14 @@ def test_warning_trace_function_changed(self):
print("Goodbye")
""")
out = self.run_command("coverage run --timid settrace.py")
- self.assertIn("Hello\n", out)
- self.assertIn("Goodbye\n", out)
+ assert "Hello\n" in out
+ assert "Goodbye\n" in out
- self.assertIn("Trace function changed", out)
+ assert "Trace function changed" in out
+ # When meta-coverage testing, this test doesn't work, because it finds
+ # coverage.py's own trace function.
+ @pytest.mark.skipif(env.METACOV, reason="Can't test timid during coverage measurement.")
def test_timid(self):
# Test that the --timid command line argument properly swaps the tracer
# function for a simpler one.
@@ -641,11 +663,6 @@ def test_timid(self):
# an environment variable set in igor.py to know whether to expect to see
# the C trace function or not.
- # When meta-coverage testing, this test doesn't work, because it finds
- # coverage.py's own trace function.
- if os.environ.get('COVERAGE_COVERAGE', ''):
- self.skipTest("Can't test timid during coverage measurement.")
-
self.make_file("showtrace.py", """\
# Show the current frame's trace function, so that we can test what the
# command-line options do to the trace function used.
@@ -674,21 +691,21 @@ def test_timid(self):
# When running without coverage, no trace function
py_out = self.run_command("python showtrace.py")
- self.assertEqual(py_out, "None\n")
+ assert py_out == "None\n"
cov_out = self.run_command("coverage run showtrace.py")
if os.environ.get('COVERAGE_TEST_TRACER', 'c') == 'c':
# If the C trace function is being tested, then regular running should have
# the C function, which registers itself as f_trace.
- self.assertEqual(cov_out, "CTracer\n")
+ assert cov_out == "CTracer\n"
else:
# If the Python trace function is being tested, then regular running will
# also show the Python function.
- self.assertEqual(cov_out, "PyTracer\n")
+ assert cov_out == "PyTracer\n"
# When running timidly, the trace function is always Python.
timid_out = self.run_command("coverage run --timid showtrace.py")
- self.assertEqual(timid_out, "PyTracer\n")
+ assert timid_out == "PyTracer\n"
def test_warn_preimported(self):
self.make_file("hello.py", """\
@@ -706,22 +723,19 @@ def f():
goodbye_path = os.path.abspath("goodbye.py")
out = self.run_command("python hello.py")
- self.assertIn("Goodbye!", out)
+ assert "Goodbye!" in out
msg = (
"Coverage.py warning: "
"Already imported a file that will be measured: {0} "
"(already-imported)").format(goodbye_path)
- self.assertIn(msg, out)
+ assert msg in out
@pytest.mark.expensive
- def test_fullcoverage(self): # pragma: no metacov
- if env.PY2: # This doesn't work on Python 2.
- self.skipTest("fullcoverage doesn't work on Python 2.")
- # It only works with the C tracer, and if we aren't measuring ourselves.
- if not env.C_TRACER or env.METACOV:
- self.skipTest("fullcoverage only works with the C tracer.")
-
+ @pytest.mark.skipif(env.METACOV, reason="Can't test fullcoverage when measuring ourselves")
+ @pytest.mark.skipif(env.PY2, reason="fullcoverage doesn't work on Python 2.")
+ @pytest.mark.skipif(not env.C_TRACER, reason="fullcoverage only works with the C tracer.")
+ def test_fullcoverage(self):
# fullcoverage is a trick to get stdlib modules measured from
# the very beginning of the process. Here we import os and
# then check how many lines are measured.
@@ -736,22 +750,21 @@ def test_fullcoverage(self): # pragma: no metacov
self.set_environ("FOOEY", "BOO")
self.set_environ("PYTHONPATH", fullcov)
out = self.run_command("python -m coverage run -L getenv.py")
- self.assertEqual(out, "FOOEY == BOO\n")
+ assert out == "FOOEY == BOO\n"
data = coverage.CoverageData()
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(line_counts(data)['os.py'], 50)
+ assert line_counts(data)['os.py'] > 50
@xfail(
env.PYPY3 and (env.PYPYVERSION >= (7, 1, 1)),
"https://bitbucket.org/pypy/pypy/issues/3074"
)
+ # Jython as of 2.7.1rc3 won't compile a filename that isn't utf8.
+ @pytest.mark.skipif(env.JYTHON, reason="Jython can't handle this test")
def test_lang_c(self):
- 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")
# LANG=C forces getfilesystemencoding on Linux to 'ascii', which causes
# failures with non-ascii file names. We don't want to make a real file
# with strange characters, though, because that gets the test runners
@@ -766,7 +779,7 @@ def test_lang_c(self):
""")
self.set_environ("LANG", "C")
out = self.run_command("coverage run weird_file.py")
- self.assertEqual(out, "1\n2\n")
+ assert out == "1\n2\n"
def test_deprecation_warnings(self):
# Test that coverage doesn't trigger deprecation warnings.
@@ -783,7 +796,7 @@ def test_deprecation_warnings(self):
self.del_environ("COVERAGE_TESTING")
out = self.run_command("python allok.py")
- self.assertEqual(out, "No warnings!\n")
+ assert out == "No warnings!\n"
def test_run_twice(self):
# https://github.com/nedbat/coveragepy/issues/353
@@ -805,18 +818,18 @@ def foo():
inst.save()
""")
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 "
+ expected = (
+ "Run 1\n" +
+ "Run 2\n" +
+ "Coverage.py warning: Module foo was previously imported, but not measured " +
"(module-not-measured)\n"
)
+ assert expected == out
def test_module_name(self):
# https://github.com/nedbat/coveragepy/issues/478
out = self.run_command("python -m coverage")
- self.assertIn("Use 'coverage help' for help", out)
+ assert "Use 'coverage help' for help" in out
TRY_EXECFILE = os.path.join(os.path.dirname(__file__), "modules/process_test/try_execfile.py")
@@ -832,14 +845,14 @@ def assert_tryexecfile_output(self, expected, actual):
"""
# First, is this even credible try_execfile.py output?
- self.assertIn('"DATA": "xyzzy"', actual)
+ assert '"DATA": "xyzzy"' in actual
if env.JYTHON: # pragma: only jython
# Argv0 is different for Jython, remove that from the comparison.
expected = re_lines(expected, r'\s+"argv0":', match=False)
actual = re_lines(actual, r'\s+"argv0":', match=False)
- self.assertMultiLineEqual(expected, actual)
+ assert expected == actual
def test_coverage_run_is_like_python(self):
with open(TRY_EXECFILE) as f:
@@ -861,9 +874,10 @@ def test_coverage_run_dashm_is_like_python_dashm(self):
actual = self.run_command("coverage run -m process_test.try_execfile")
self.assert_tryexecfile_output(expected, actual)
+ @pytest.mark.skipif(env.PYVERSION == (3, 5, 4, 'final', 0, 0),
+ reason="3.5.4 broke this: https://bugs.python.org/issue32551"
+ )
def test_coverage_run_dir_is_like_python_dir(self):
- if env.PYVERSION == (3, 5, 4, 'final', 0, 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())
@@ -891,9 +905,10 @@ def test_coverage_run_dashm_dir_no_init_is_like_python(self):
else:
self.assert_tryexecfile_output(expected, actual)
+ @pytest.mark.skipif(env.PY2,
+ reason="Python 2 runs __main__ twice, I can't be bothered to make it work."
+ )
def test_coverage_run_dashm_dir_with_init_is_like_python(self):
- if env.PY2:
- self.skipTest("Python 2 runs __main__ twice, I can't be bothered to make it work.")
with open(TRY_EXECFILE) as f:
self.make_file("with_main/__main__.py", f.read())
self.make_file("with_main/__init__.py", "")
@@ -936,8 +951,8 @@ def test_coverage_run_dashm_superset_of_doubledashsource(self):
self.assert_tryexecfile_output(expected, actual)
st, out = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
- self.assertEqual(self.line_count(out), 6, out)
+ assert st == 0
+ assert self.line_count(out) == 6, out
def test_coverage_run_script_imports_doubledashsource(self):
# This file imports try_execfile, which compiles it to .pyc, so the
@@ -955,8 +970,8 @@ def test_coverage_run_script_imports_doubledashsource(self):
self.assert_tryexecfile_output(expected, actual)
st, out = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
- self.assertEqual(self.line_count(out), 6, out)
+ assert st == 0
+ assert self.line_count(out) == 6, out
def test_coverage_run_dashm_is_like_python_dashm_off_path(self):
# https://github.com/nedbat/coveragepy/issues/242
@@ -974,7 +989,7 @@ def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self):
self.make_file("package/__main__.py", "print('main')")
expected = self.run_command("python -m package")
actual = self.run_command("coverage run -m package")
- self.assertMultiLineEqual(expected, actual)
+ assert expected == actual
def test_coverage_zip_is_like_python(self):
# Test running coverage from a zip file itself. Some environments
@@ -1015,14 +1030,13 @@ def test_coverage_custom_script(self):
""")
# If this test fails, it will be with "can't import thing".
out = self.run_command("python run_coverage.py run how_is_it.py")
- self.assertIn("hello-xyzzy", out)
+ assert "hello-xyzzy" in out
out = self.run_command("python -m run_coverage run how_is_it.py")
- self.assertIn("hello-xyzzy", out)
+ assert "hello-xyzzy" in out
+ @pytest.mark.skipif(env.WINDOWS, reason="Windows can't make symlinks")
def test_bug_862(self):
- if env.WINDOWS:
- self.skipTest("Windows can't make symlinks")
# This simulates how pyenv and pyenv-virtualenv end up creating the
# coverage executable.
self.make_file("elsewhere/bin/fake-coverage", """\
@@ -1035,7 +1049,7 @@ def test_bug_862(self):
self.make_file("foo.py", "print('inside foo')")
self.make_file("bar.py", "import foo")
out = self.run_command("somewhere/bin/fake-coverage run bar.py")
- self.assertEqual("inside foo\n", out)
+ assert "inside foo\n" == out
def test_bug_909(self):
# https://github.com/nedbat/coveragepy/issues/909
@@ -1086,21 +1100,22 @@ def excepthook(*args):
cov_st, cov_out = self.run_command_status("coverage run excepthook.py")
py_st, py_out = self.run_command_status("python excepthook.py")
if not env.JYTHON:
- self.assertEqual(cov_st, py_st)
- self.assertEqual(cov_st, 1)
+ assert cov_st == py_st
+ assert cov_st == 1
- self.assertIn("in excepthook", py_out)
- self.assertEqual(cov_out, py_out)
+ assert "in excepthook" in py_out
+ assert cov_out == py_out
# Read the coverage file and see that excepthook.py has 7 lines
# executed.
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['excepthook.py'], 7)
+ assert line_counts(data)['excepthook.py'] == 7
+ @pytest.mark.skipif(not env.CPYTHON,
+ reason="non-CPython handles excepthook exits differently, punt for now."
+ )
def test_excepthook_exit(self):
- if not env.CPYTHON:
- self.skipTest("non-CPython handles excepthook exits differently, punt for now.")
self.make_file("excepthook_exit.py", """\
import sys
@@ -1114,15 +1129,14 @@ def excepthook(*args):
""")
cov_st, cov_out = self.run_command_status("coverage run excepthook_exit.py")
py_st, py_out = self.run_command_status("python excepthook_exit.py")
- self.assertEqual(cov_st, py_st)
- self.assertEqual(cov_st, 0)
+ assert cov_st == py_st
+ assert cov_st == 0
- self.assertIn("in excepthook", py_out)
- self.assertEqual(cov_out, py_out)
+ assert "in excepthook" in py_out
+ assert cov_out == py_out
+ @pytest.mark.skipif(env.PYPY, reason="PyPy handles excepthook throws differently.")
def test_excepthook_throw(self):
- if env.PYPY:
- self.skipTest("PyPy handles excepthook throws differently, punt for now.")
self.make_file("excepthook_throw.py", """\
import sys
@@ -1140,41 +1154,37 @@ def excepthook(*args):
cov_st, cov_out = self.run_command_status("coverage run excepthook_throw.py")
py_st, py_out = self.run_command_status("python excepthook_throw.py")
if not env.JYTHON:
- self.assertEqual(cov_st, py_st)
- self.assertEqual(cov_st, 1)
+ assert cov_st == py_st
+ assert cov_st == 1
- self.assertIn("in excepthook", py_out)
- self.assertEqual(cov_out, py_out)
+ assert "in excepthook" in py_out
+ assert cov_out == py_out
+@pytest.mark.skipif(env.JYTHON, reason="Coverage command names don't work on Jython")
class AliasedCommandTest(CoverageTest):
"""Tests of the version-specific command aliases."""
run_in_temp_dir = False
- def setUp(self):
- if env.JYTHON:
- self.skipTest("Coverage command names don't work on Jython")
- super(AliasedCommandTest, self).setUp()
-
def test_major_version_works(self):
# "coverage2" works on py2
cmd = "coverage%d" % sys.version_info[0]
out = self.run_command(cmd)
- self.assertIn("Code coverage for Python", out)
+ assert "Code coverage for Python" in out
def test_wrong_alias_doesnt_work(self):
# "coverage3" doesn't work on py2
assert sys.version_info[0] in [2, 3] # Let us know when Python 4 is out...
badcmd = "coverage%d" % (5 - sys.version_info[0])
out = self.run_command(badcmd)
- self.assertNotIn("Code coverage for Python", out)
+ assert "Code coverage for Python" not in out
def test_specific_alias_works(self):
# "coverage-2.7" works on py2.7
cmd = "coverage-%d.%d" % sys.version_info[:2]
out = self.run_command(cmd)
- self.assertIn("Code coverage for Python", out)
+ assert "Code coverage for Python" in out
def test_aliases_used_in_messages(self):
cmds = [
@@ -1184,8 +1194,8 @@ def test_aliases_used_in_messages(self):
]
for cmd in cmds:
out = self.run_command("%s foobar" % cmd)
- self.assertIn("Unknown command: 'foobar'", out)
- self.assertIn("Use '%s help' for help" % cmd, out)
+ assert "Unknown command: 'foobar'" in out
+ assert "Use '%s help' for help" % cmd in out
class PydocTest(CoverageTest):
@@ -1199,11 +1209,11 @@ def assert_pydoc_ok(self, name, thing):
out = self.run_command("python -m pydoc " + name)
# It should say "Help on..", and not have a traceback
self.assert_starts_with(out, "Help on ")
- self.assertNotIn("Traceback", out)
+ assert "Traceback" not in out
# All of the lines in the docstring should be there somewhere.
for line in thing.__doc__.splitlines():
- self.assertIn(line.strip(), out)
+ assert line.strip() in out
def test_pydoc_coverage(self):
self.assert_pydoc_ok("coverage", coverage)
@@ -1228,29 +1238,25 @@ def setUp(self):
e = 7
""")
st, _ = self.run_command_status("coverage run --source=. forty_two_plus.py")
- self.assertEqual(st, 0)
+ assert st == 0
def test_report_43_is_ok(self):
st, out = self.run_command_status("coverage report --fail-under=43")
- self.assertEqual(st, 0)
- self.assertEqual(self.last_line_squeezed(out), "TOTAL 7 4 43%")
+ assert st == 0
+ assert self.last_line_squeezed(out) == "TOTAL 7 4 43%"
def test_report_43_is_not_ok(self):
st, out = self.run_command_status("coverage report --fail-under=44")
- self.assertEqual(st, 2)
- self.assertEqual(
- self.last_line_squeezed(out),
- "Coverage failure: total of 43 is less than fail-under=44"
- )
+ assert st == 2
+ expected = "Coverage failure: total of 43 is less than fail-under=44"
+ assert expected == self.last_line_squeezed(out)
def test_report_42p86_is_not_ok(self):
self.make_file(".coveragerc", "[report]\nprecision = 2")
st, out = self.run_command_status("coverage report --fail-under=42.88")
- self.assertEqual(st, 2)
- self.assertEqual(
- self.last_line_squeezed(out),
- "Coverage failure: total of 42.86 is less than fail-under=42.88"
- )
+ assert st == 2
+ expected = "Coverage failure: total of 42.86 is less than fail-under=42.88"
+ assert expected == self.last_line_squeezed(out)
class FailUnderNoFilesTest(CoverageTest):
@@ -1258,8 +1264,8 @@ class FailUnderNoFilesTest(CoverageTest):
def test_report(self):
self.make_file(".coveragerc", "[report]\nfail_under = 99\n")
st, out = self.run_command_status("coverage report")
- self.assertIn('No data to report.', out)
- self.assertEqual(st, 1)
+ assert 'No data to report.' in out
+ assert st == 1
class FailUnderEmptyFilesTest(CoverageTest):
@@ -1268,40 +1274,36 @@ def test_report(self):
self.make_file(".coveragerc", "[report]\nfail_under = 99\n")
self.make_file("empty.py", "")
st, _ = self.run_command_status("coverage run empty.py")
- self.assertEqual(st, 0)
+ assert st == 0
st, _ = self.run_command_status("coverage report")
- self.assertEqual(st, 2)
+ assert st == 2
+@pytest.mark.skipif(env.JYTHON, reason="Jython doesn't like accented file names")
class UnicodeFilePathsTest(CoverageTest):
"""Tests of using non-ascii characters in the names of files."""
- def setUp(self):
- if env.JYTHON:
- self.skipTest("Jython doesn't like accented file names")
- super(UnicodeFilePathsTest, self).setUp()
-
def test_accented_dot_py(self):
# Make a file with a non-ascii character in the filename.
self.make_file(u"h\xe2t.py", "print('accented')")
out = self.run_command(u"coverage run --source=. h\xe2t.py")
- self.assertEqual(out, "accented\n")
+ assert out == "accented\n"
# The HTML report uses ascii-encoded HTML entities.
out = self.run_command("coverage html")
- self.assertEqual(out, "")
+ assert out == ""
self.assert_exists(u"htmlcov/h\xe2t_py.html")
with open("htmlcov/index.html") as indexf:
index = indexf.read()
- self.assertIn('hât.py', index)
+ assert 'hât.py' in index
# The XML report is always UTF8-encoded.
out = self.run_command("coverage xml")
- self.assertEqual(out, "")
+ assert out == ""
with open("coverage.xml", "rb") as xmlf:
xml = xmlf.read()
- self.assertIn(u' filename="h\xe2t.py"'.encode('utf8'), xml)
- self.assertIn(u' name="h\xe2t.py"'.encode('utf8'), xml)
+ assert u' filename="h\xe2t.py"'.encode('utf8') in xml
+ assert u' name="h\xe2t.py"'.encode('utf8') in xml
report_expected = (
u"Name Stmts Miss Cover\n"
@@ -1315,29 +1317,29 @@ def test_accented_dot_py(self):
report_expected = report_expected.encode(output_encoding())
out = self.run_command("coverage report")
- self.assertEqual(out, report_expected)
+ assert out == report_expected
def test_accented_directory(self):
# Make a file with a non-ascii character in the directory name.
self.make_file(u"\xe2/accented.py", "print('accented')")
out = self.run_command(u"coverage run --source=. \xe2/accented.py")
- self.assertEqual(out, "accented\n")
+ assert out == "accented\n"
# The HTML report uses ascii-encoded HTML entities.
out = self.run_command("coverage html")
- self.assertEqual(out, "")
+ assert out == ""
self.assert_exists(u"htmlcov/\xe2_accented_py.html")
with open("htmlcov/index.html") as indexf:
index = indexf.read()
- self.assertIn('â%saccented.py' % os.sep, index)
+ assert 'â%saccented.py' % os.sep in index
# The XML report is always UTF8-encoded.
out = self.run_command("coverage xml")
- self.assertEqual(out, "")
+ assert out == ""
with open("coverage.xml", "rb") as xmlf:
xml = xmlf.read()
- self.assertIn(b' filename="\xc3\xa2/accented.py"', xml)
- self.assertIn(b' name="accented.py"', xml)
+ assert b' filename="\xc3\xa2/accented.py"' in xml
+ assert b' name="accented.py"' in xml
dom = ElementTree.parse("coverage.xml")
elts = dom.findall(u".//package[@name='â']")
@@ -1361,17 +1363,13 @@ def test_accented_directory(self):
report_expected = report_expected.encode(output_encoding())
out = self.run_command("coverage report")
- self.assertEqual(out, report_expected)
+ assert out == report_expected
+@pytest.mark.skipif(env.WINDOWS, reason="Windows can't delete the directory in use.")
class YankedDirectoryTest(CoverageTest):
"""Tests of what happens when the current directory is deleted."""
- def setUp(self):
- if env.WINDOWS:
- self.skipTest("Windows can't delete the directory in use.")
- super(YankedDirectoryTest, self).setUp()
-
BUG_806 = """\
import os
import sys
@@ -1386,18 +1384,18 @@ def setUp(self):
def test_removing_directory(self):
self.make_file("bug806.py", self.BUG_806)
out = self.run_command("coverage run bug806.py noerror")
- self.assertEqual(out, "noerror\n")
+ assert out == "noerror\n"
def test_removing_directory_with_error(self):
self.make_file("bug806.py", self.BUG_806)
out = self.run_command("coverage run bug806.py")
path = python_reported_file('bug806.py')
- self.assertEqual(out, textwrap.dedent("""\
+ assert out == textwrap.dedent("""\
Traceback (most recent call last):
File "{}", line 8, in
print(sys.argv[1])
IndexError: list index out of range
- """.format(path)))
+ """.format(path))
def possible_pth_dirs():
@@ -1454,7 +1452,7 @@ def setUp(self):
super(ProcessCoverageMixin, self).setUp()
# Create the .pth file.
- self.assertTrue(PTH_DIR)
+ assert PTH_DIR
pth_contents = "import coverage; coverage.process_startup()\n"
pth_path = os.path.join(PTH_DIR, "subcover_{}.pth".format(WORKER))
with open(pth_path, "w") as pth:
@@ -1464,6 +1462,7 @@ def setUp(self):
self.addCleanup(persistent_remove, self.pth_path)
+@pytest.mark.skipif(env.METACOV, reason="Can't test sub-process pth file during metacoverage")
class ProcessStartupTest(ProcessCoverageMixin, CoverageTest):
"""Test that we can measure coverage in sub-processes."""
@@ -1483,10 +1482,7 @@ def setUp(self):
f.close()
""")
- def test_subprocess_with_pth_files(self): # pragma: no metacov
- if env.METACOV:
- self.skipTest("Can't test sub-process pth file suppport during metacoverage")
-
+ def test_subprocess_with_pth_files(self):
# An existing data file should not be read when a subprocess gets
# measured automatically. Create the data file here with bogus data in
# it.
@@ -1499,22 +1495,19 @@ def test_subprocess_with_pth_files(self): # pragma: no metacov
data_file = .mycovdata
""")
self.set_environ("COVERAGE_PROCESS_START", "coverage.ini")
- import main # pylint: disable=unused-import
+ import main # pylint: disable=unused-import, import-error
with open("out.txt") as f:
- self.assertEqual(f.read(), "Hello, world!\n")
+ assert f.read() == "Hello, world!\n"
# Read the data from .coverage
self.assert_exists(".mycovdata")
data = coverage.CoverageData(".mycovdata")
data.read()
- self.assertEqual(line_counts(data)['sub.py'], 3)
+ assert line_counts(data)['sub.py'] == 3
- def test_subprocess_with_pth_files_and_parallel(self): # pragma: no metacov
+ def test_subprocess_with_pth_files_and_parallel(self):
# https://github.com/nedbat/coveragepy/issues/492
- if env.METACOV:
- self.skipTest("Can't test sub-process pth file suppport during metacoverage")
-
self.make_file("coverage.ini", """\
[run]
parallel = true
@@ -1524,7 +1517,7 @@ def test_subprocess_with_pth_files_and_parallel(self): # pragma: no metacov
self.run_command("coverage run main.py")
with open("out.txt") as f:
- self.assertEqual(f.read(), "Hello, world!\n")
+ assert f.read() == "Hello, world!\n"
self.run_command("coverage combine")
@@ -1532,13 +1525,15 @@ def test_subprocess_with_pth_files_and_parallel(self): # pragma: no metacov
self.assert_exists(".coverage")
data = coverage.CoverageData()
data.read()
- self.assertEqual(line_counts(data)['sub.py'], 3)
+ assert 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*')
- self.assertEqual(len(data_files), 1,
- "Expected only .coverage after combine, looks like there are "
- "extra data files that were not cleaned up: %r" % data_files)
+ msg = (
+ "Expected only .coverage after combine, looks like there are " +
+ "extra data files that were not cleaned up: %r" % data_files
+ )
+ assert len(data_files) == 1, msg
class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest):
@@ -1557,7 +1552,7 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest):
def assert_pth_and_source_work_together(
self, dashm, package, source
- ): # pragma: no metacov
+ ):
"""Run the test for a particular combination of factors.
The arguments are all strings:
@@ -1572,9 +1567,6 @@ def assert_pth_and_source_work_together(
``--source`` argument.
"""
- if env.METACOV:
- self.skipTest("Can't test sub-process pth file support during metacoverage")
-
def fullname(modname):
"""What is the full module name for `modname` for this test?"""
if package and dashm:
@@ -1616,7 +1608,7 @@ def path(basename):
self.run_command(cmd)
with open("out.txt") as f:
- self.assertEqual(f.read(), "Hello, world!")
+ assert f.read() == "Hello, world!"
# Read the data from .coverage
self.assert_exists(".coverage")
@@ -1624,8 +1616,8 @@ def path(basename):
data.read()
summary = line_counts(data)
print(summary)
- self.assertEqual(summary[source + '.py'], 3)
- self.assertEqual(len(summary), 1)
+ assert summary[source + '.py'] == 3
+ assert len(summary) == 1
def test_dashm_main(self):
self.assert_pth_and_source_work_together('-m', '', 'main')
diff --git a/tests/test_python.py b/tests/test_python.py
index 441ef499a..0175f5afd 100644
--- a/tests/test_python.py
+++ b/tests/test_python.py
@@ -28,7 +28,7 @@ def test_get_encoded_zip_files(self):
filename = filename.replace("/", os.sep)
zip_data = get_zip_bytes(filename)
zip_text = zip_data.decode(encoding)
- self.assertIn('All OK', zip_text)
+ assert 'All OK' in zip_text
# Run the code to see that we really got it encoded properly.
__import__("encoded_"+encoding)
diff --git a/tests/test_results.py b/tests/test_results.py
index 377c150bd..0453424b4 100644
--- a/tests/test_results.py
+++ b/tests/test_results.py
@@ -18,30 +18,30 @@ class NumbersTest(CoverageTest):
def test_basic(self):
n1 = Numbers(n_files=1, n_statements=200, n_missing=20)
- self.assertEqual(n1.n_statements, 200)
- self.assertEqual(n1.n_executed, 180)
- self.assertEqual(n1.n_missing, 20)
- self.assertEqual(n1.pc_covered, 90)
+ assert n1.n_statements == 200
+ assert n1.n_executed == 180
+ assert n1.n_missing == 20
+ assert n1.pc_covered == 90
def test_addition(self):
n1 = Numbers(n_files=1, n_statements=200, n_missing=20)
n2 = Numbers(n_files=1, n_statements=10, n_missing=8)
n3 = n1 + n2
- self.assertEqual(n3.n_files, 2)
- self.assertEqual(n3.n_statements, 210)
- self.assertEqual(n3.n_executed, 182)
- self.assertEqual(n3.n_missing, 28)
- self.assertAlmostEqual(n3.pc_covered, 86.666666666)
+ assert n3.n_files == 2
+ assert n3.n_statements == 210
+ assert n3.n_executed == 182
+ assert n3.n_missing == 28
+ assert round(abs(n3.pc_covered-86.666666666), 7) == 0
def test_sum(self):
n1 = Numbers(n_files=1, n_statements=200, n_missing=20)
n2 = Numbers(n_files=1, n_statements=10, n_missing=8)
n3 = sum([n1, n2])
- self.assertEqual(n3.n_files, 2)
- self.assertEqual(n3.n_statements, 210)
- self.assertEqual(n3.n_executed, 182)
- self.assertEqual(n3.n_missing, 28)
- self.assertAlmostEqual(n3.pc_covered, 86.666666666)
+ assert n3.n_files == 2
+ assert n3.n_statements == 210
+ assert n3.n_executed == 182
+ assert n3.n_missing == 28
+ assert round(abs(n3.pc_covered-86.666666666), 7) == 0
def test_pc_covered_str(self):
# Numbers._precision is a global, which is bad.
@@ -50,10 +50,10 @@ def test_pc_covered_str(self):
n1 = Numbers(n_files=1, n_statements=1000, n_missing=1)
n999 = Numbers(n_files=1, n_statements=1000, n_missing=999)
n1000 = Numbers(n_files=1, n_statements=1000, n_missing=1000)
- self.assertEqual(n0.pc_covered_str, "100")
- self.assertEqual(n1.pc_covered_str, "99")
- self.assertEqual(n999.pc_covered_str, "1")
- self.assertEqual(n1000.pc_covered_str, "0")
+ assert n0.pc_covered_str == "100"
+ assert n1.pc_covered_str == "99"
+ assert n999.pc_covered_str == "1"
+ assert n1000.pc_covered_str == "0"
def test_pc_covered_str_precision(self):
# Numbers._precision is a global, which is bad.
@@ -62,21 +62,21 @@ def test_pc_covered_str_precision(self):
n1 = Numbers(n_files=1, n_statements=10000, n_missing=1)
n9999 = Numbers(n_files=1, n_statements=10000, n_missing=9999)
n10000 = Numbers(n_files=1, n_statements=10000, n_missing=10000)
- self.assertEqual(n0.pc_covered_str, "100.0")
- self.assertEqual(n1.pc_covered_str, "99.9")
- self.assertEqual(n9999.pc_covered_str, "0.1")
- self.assertEqual(n10000.pc_covered_str, "0.0")
+ assert n0.pc_covered_str == "100.0"
+ assert n1.pc_covered_str == "99.9"
+ assert n9999.pc_covered_str == "0.1"
+ assert n10000.pc_covered_str == "0.0"
Numbers.set_precision(0)
def test_covered_ratio(self):
n = Numbers(n_files=1, n_statements=200, n_missing=47)
- self.assertEqual(n.ratio_covered, (153, 200))
+ assert n.ratio_covered == (153, 200)
n = Numbers(
n_files=1, n_statements=200, n_missing=47,
n_branches=10, n_missing_branches=3, n_partial_branches=1000,
)
- self.assertEqual(n.ratio_covered, (160, 210))
+ assert n.ratio_covered == (160, 210)
@pytest.mark.parametrize("total, fail_under, precision, result", [
diff --git a/tests/test_setup.py b/tests/test_setup.py
index 9ab103914..febc383ea 100644
--- a/tests/test_setup.py
+++ b/tests/test_setup.py
@@ -24,12 +24,12 @@ def test_metadata(self):
status, output = self.run_command_status(
"python setup.py --description --version --url --author"
)
- self.assertEqual(status, 0)
+ assert status == 0
out = output.splitlines()
- self.assertIn("measurement", out[0])
- self.assertEqual(coverage.__version__, out[1])
- self.assertIn("github.com/nedbat/coveragepy", out[2])
- self.assertIn("Ned Batchelder", out[3])
+ assert "measurement" in out[0]
+ assert coverage.__version__ == out[1]
+ assert "github.com/nedbat/coveragepy" in out[2]
+ assert "Ned Batchelder" in out[3]
def test_more_metadata(self):
# Let's be sure we pick up our own setup.py
@@ -38,12 +38,12 @@ def test_more_metadata(self):
from setup import setup_args
classifiers = setup_args['classifiers']
- self.assertGreater(len(classifiers), 7)
+ assert len(classifiers) > 7
self.assert_starts_with(classifiers[-1], "Development Status ::")
- self.assertIn("Programming Language :: Python :: %d" % sys.version_info[:1], classifiers)
- self.assertIn("Programming Language :: Python :: %d.%d" % sys.version_info[:2], classifiers)
+ assert "Programming Language :: Python :: %d" % sys.version_info[:1] in classifiers
+ assert "Programming Language :: Python :: %d.%d" % sys.version_info[:2] in classifiers
long_description = setup_args['long_description'].splitlines()
- self.assertGreater(len(long_description), 7)
- self.assertNotEqual(long_description[0].strip(), "")
- self.assertNotEqual(long_description[-1].strip(), "")
+ assert len(long_description) > 7
+ assert long_description[0].strip() != ""
+ assert long_description[-1].strip() != ""
diff --git a/tests/test_summary.py b/tests/test_summary.py
index feaa0fe0b..8596c45c4 100644
--- a/tests/test_summary.py
+++ b/tests/test_summary.py
@@ -10,6 +10,8 @@
import py_compile
import re
+import pytest
+
import coverage
from coverage import env
from coverage.backward import StringIO
@@ -44,7 +46,7 @@ def omit_site_packages(self):
def test_report(self):
self.make_mycode()
out = self.run_command("coverage run mycode.py")
- self.assertEqual(out, 'done\n')
+ assert out == 'done\n'
report = self.report_from_command("coverage report")
# Name Stmts Miss Cover
@@ -55,11 +57,11 @@ def test_report(self):
# ------------------------------------------------------------------
# TOTAL 8 0 100%
- self.assertNotIn("/coverage/__init__/", report)
- self.assertIn("/tests/modules/covmod1.py ", report)
- self.assertIn("/tests/zipmods.zip/covmodzip1.py ", report)
- self.assertIn("mycode.py ", report)
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 8 0 100%")
+ assert "/coverage/__init__/" not in report
+ assert "/tests/modules/covmod1.py " in report
+ assert "/tests/zipmods.zip/covmodzip1.py " in report
+ assert "mycode.py " in report
+ assert self.last_line_squeezed(report) == "TOTAL 8 0 100%"
def test_report_just_one(self):
# Try reporting just one module
@@ -73,12 +75,12 @@ def test_report_just_one(self):
# -------------------------------
# TOTAL 4 0 100%
- self.assertEqual(self.line_count(report), 5)
- self.assertNotIn("/coverage/", report)
- self.assertNotIn("/tests/modules/covmod1.py ", report)
- self.assertNotIn("/tests/zipmods.zip/covmodzip1.py ", report)
- self.assertIn("mycode.py ", report)
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 4 0 100%")
+ assert self.line_count(report) == 5
+ assert "/coverage/" not in report
+ assert "/tests/modules/covmod1.py " not in report
+ assert "/tests/zipmods.zip/covmodzip1.py " not in report
+ assert "mycode.py " in report
+ assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_report_wildcard(self):
# Try reporting using wildcards to get the modules.
@@ -92,12 +94,12 @@ def test_report_wildcard(self):
# -------------------------------
# TOTAL 4 0 100%
- self.assertEqual(self.line_count(report), 5)
- self.assertNotIn("/coverage/", report)
- self.assertNotIn("/tests/modules/covmod1.py ", report)
- self.assertNotIn("/tests/zipmods.zip/covmodzip1.py ", report)
- self.assertIn("mycode.py ", report)
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 4 0 100%")
+ assert self.line_count(report) == 5
+ assert "/coverage/" not in report
+ assert "/tests/modules/covmod1.py " not in report
+ assert "/tests/zipmods.zip/covmodzip1.py " not in report
+ assert "mycode.py " in report
+ assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_report_omitting(self):
# Try reporting while omitting some modules
@@ -112,12 +114,12 @@ def test_report_omitting(self):
# -------------------------------
# TOTAL 4 0 100%
- self.assertEqual(self.line_count(report), 5)
- self.assertNotIn("/coverage/", report)
- self.assertNotIn("/tests/modules/covmod1.py ", report)
- self.assertNotIn("/tests/zipmods.zip/covmodzip1.py ", report)
- self.assertIn("mycode.py ", report)
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 4 0 100%")
+ assert self.line_count(report) == 5
+ assert "/coverage/" not in report
+ assert "/tests/modules/covmod1.py " not in report
+ assert "/tests/zipmods.zip/covmodzip1.py " not in report
+ assert "mycode.py " in report
+ assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_report_including(self):
# Try reporting while including some modules
@@ -131,12 +133,12 @@ def test_report_including(self):
# -------------------------------
# TOTAL 4 0 100%
- self.assertEqual(self.line_count(report), 5)
- self.assertNotIn("/coverage/", report)
- self.assertNotIn("/tests/modules/covmod1.py ", report)
- self.assertNotIn("/tests/zipmods.zip/covmodzip1.py ", report)
- self.assertIn("mycode.py ", report)
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 4 0 100%")
+ assert self.line_count(report) == 5
+ assert "/coverage/" not in report
+ assert "/tests/modules/covmod1.py " not in report
+ assert "/tests/zipmods.zip/covmodzip1.py " not in report
+ assert "mycode.py " in report
+ assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_run_source_vs_report_include(self):
# https://github.com/nedbat/coveragepy/issues/621
@@ -170,8 +172,8 @@ def test_run_omit_vs_report_omit(self):
covdata = CoverageData()
covdata.read()
files = [os.path.basename(p) for p in covdata.measured_files()]
- self.assertIn("covmod1.py", files)
- self.assertNotIn("covmodzip1.py", files)
+ assert "covmod1.py" in files
+ assert "covmodzip1.py" not in files
def test_report_branches(self):
self.make_file("mybranch.py", """\
@@ -182,7 +184,7 @@ def branch(x):
branch(1)
""")
out = self.run_command("coverage run --source=. --branch mybranch.py")
- self.assertEqual(out, 'x\n')
+ assert out == 'x\n'
report = self.report_from_command("coverage report")
# Name Stmts Miss Branch BrPart Cover
@@ -191,9 +193,9 @@ def branch(x):
# -----------------------------------------------
# TOTAL 5 0 2 1 86%
- self.assertEqual(self.line_count(report), 5)
- self.assertIn("mybranch.py ", report)
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 5 0 2 1 86%")
+ assert self.line_count(report) == 5
+ assert "mybranch.py " in report
+ assert self.last_line_squeezed(report) == "TOTAL 5 0 2 1 86%"
def test_report_show_missing(self):
self.make_file("mymissing.py", """\
@@ -213,7 +215,7 @@ def missing(x, y):
missing(0, 1)
""")
out = self.run_command("coverage run --source=. mymissing.py")
- self.assertEqual(out, 'y\nz\n')
+ assert out == 'y\nz\n'
report = self.report_from_command("coverage report --show-missing")
# Name Stmts Miss Cover Missing
@@ -222,10 +224,10 @@ def missing(x, y):
# --------------------------------------------
# TOTAL 14 3 79% 3-4, 10
- self.assertEqual(self.line_count(report), 5)
+ assert self.line_count(report) == 5
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[2], "mymissing.py 14 3 79% 3-4, 10")
- self.assertEqual(squeezed[4], "TOTAL 14 3 79%")
+ assert squeezed[2] == "mymissing.py 14 3 79% 3-4, 10"
+ assert squeezed[4] == "TOTAL 14 3 79%"
def test_report_show_missing_branches(self):
self.make_file("mybranch.py", """\
@@ -238,7 +240,7 @@ def branch(x, y):
""")
self.omit_site_packages()
out = self.run_command("coverage run --branch mybranch.py")
- self.assertEqual(out, 'x\ny\n')
+ assert out == 'x\ny\n'
report = self.report_from_command("coverage report --show-missing")
# Name Stmts Miss Branch BrPart Cover Missing
@@ -247,10 +249,10 @@ def branch(x, y):
# ----------------------------------------------------------
# TOTAL 6 0 4 2 80%
- self.assertEqual(self.line_count(report), 5)
+ assert self.line_count(report) == 5
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[2], "mybranch.py 6 0 4 2 80% 2->4, 4->exit")
- self.assertEqual(squeezed[4], "TOTAL 6 0 4 2 80%")
+ assert squeezed[2] == "mybranch.py 6 0 4 2 80% 2->4, 4->exit"
+ assert squeezed[4] == "TOTAL 6 0 4 2 80%"
def test_report_show_missing_branches_and_lines(self):
self.make_file("main.py", """\
@@ -270,7 +272,7 @@ def branch(x, y, z):
""")
self.omit_site_packages()
out = self.run_command("coverage run --branch main.py")
- self.assertEqual(out, 'x\ny\n')
+ assert out == 'x\ny\n'
report = self.report_from_command("coverage report --show-missing")
report_lines = report.splitlines()
@@ -278,11 +280,11 @@ def branch(x, y, z):
'Name Stmts Miss Branch BrPart Cover Missing',
'---------------------------------------------------------',
'main.py 1 0 0 0 100%',
- 'mybranch.py 10 2 8 3 61% 2->4, 4->6, 6->7, 7-8',
+ 'mybranch.py 10 2 8 3 61% 2->4, 4->6, 7-8',
'---------------------------------------------------------',
'TOTAL 11 2 8 3 63%',
]
- self.assertEqual(expected, report_lines)
+ assert expected == report_lines
def test_report_skip_covered_no_branches(self):
self.make_file("main.py", """
@@ -298,7 +300,7 @@ def not_covered():
""")
self.omit_site_packages()
out = self.run_command("coverage run main.py")
- self.assertEqual(out, "z\n")
+ assert out == "z\n"
report = self.report_from_command("coverage report --skip-covered --fail-under=70")
# Name Stmts Miss Cover
@@ -309,12 +311,12 @@ def not_covered():
#
# 1 file skipped due to complete coverage.
- self.assertEqual(self.line_count(report), 7, report)
+ assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[2], "not_covered.py 2 1 50%")
- self.assertEqual(squeezed[4], "TOTAL 6 1 83%")
- self.assertEqual(squeezed[6], "1 file skipped due to complete coverage.")
- self.assertEqual(self.last_command_status, 0)
+ assert squeezed[2] == "not_covered.py 2 1 50%"
+ assert squeezed[4] == "TOTAL 6 1 83%"
+ assert squeezed[6] == "1 file skipped due to complete coverage."
+ assert self.last_command_status == 0
def test_report_skip_covered_branches(self):
self.make_file("main.py", """
@@ -339,7 +341,7 @@ def foo():
""")
self.omit_site_packages()
out = self.run_command("coverage run --branch main.py")
- self.assertEqual(out, "n\nz\n")
+ assert out == "n\nz\n"
report = self.report_from_command("coverage report --skip-covered")
# Name Stmts Miss Branch BrPart Cover
@@ -350,11 +352,11 @@ def foo():
#
# 2 files skipped due to complete coverage.
- self.assertEqual(self.line_count(report), 7, report)
+ assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[2], "not_covered.py 4 0 2 1 83%")
- self.assertEqual(squeezed[4], "TOTAL 13 0 4 1 94%")
- self.assertEqual(squeezed[6], "2 files skipped due to complete coverage.")
+ assert squeezed[2] == "not_covered.py 4 0 2 1 83%"
+ assert squeezed[4] == "TOTAL 13 0 4 1 94%"
+ assert squeezed[6] == "2 files skipped due to complete coverage."
def test_report_skip_covered_branches_with_totals(self):
self.make_file("main.py", """
@@ -379,7 +381,7 @@ def does_not_appear_in_this_film(ni):
""")
self.omit_site_packages()
out = self.run_command("coverage run --branch main.py")
- self.assertEqual(out, "n\nz\n")
+ assert out == "n\nz\n"
report = self.report_from_command("coverage report --skip-covered")
# Name Stmts Miss Branch BrPart Cover
@@ -391,12 +393,12 @@ def does_not_appear_in_this_film(ni):
#
# 1 file skipped due to complete coverage.
- self.assertEqual(self.line_count(report), 8, report)
+ assert self.line_count(report) == 8, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[2], "also_not_run.py 2 1 0 0 50%")
- self.assertEqual(squeezed[3], "not_covered.py 4 0 2 1 83%")
- self.assertEqual(squeezed[5], "TOTAL 13 1 4 1 88%")
- self.assertEqual(squeezed[7], "1 file skipped due to complete coverage.")
+ assert squeezed[2] == "also_not_run.py 2 1 0 0 50%"
+ assert squeezed[3] == "not_covered.py 4 0 2 1 83%"
+ assert squeezed[5] == "TOTAL 13 1 4 1 88%"
+ assert squeezed[7] == "1 file skipped due to complete coverage."
def test_report_skip_covered_all_files_covered(self):
self.make_file("main.py", """
@@ -405,7 +407,7 @@ def foo():
foo()
""")
out = self.run_command("coverage run --source=. --branch main.py")
- self.assertEqual(out, "")
+ assert out == ""
report = self.report_from_command("coverage report --skip-covered")
# Name Stmts Miss Branch BrPart Cover
@@ -415,9 +417,9 @@ def foo():
#
# 1 file skipped due to complete coverage.
- self.assertEqual(self.line_count(report), 6, report)
+ assert self.line_count(report) == 6, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[5], "1 file skipped due to complete coverage.")
+ assert squeezed[5] == "1 file skipped due to complete coverage."
def test_report_skip_covered_longfilename(self):
self.make_file("long_______________filename.py", """
@@ -426,7 +428,7 @@ def foo():
foo()
""")
out = self.run_command("coverage run --source=. --branch long_______________filename.py")
- self.assertEqual(out, "")
+ assert out == ""
report = self.report_from_command("coverage report --skip-covered")
# Name Stmts Miss Branch BrPart Cover
@@ -436,20 +438,20 @@ def foo():
#
# 1 file skipped due to complete coverage.
- self.assertEqual(self.line_count(report), 6, report)
+ assert self.line_count(report) == 6, report
lines = self.report_lines(report)
- self.assertEqual(lines[0], "Name Stmts Miss Branch BrPart Cover")
+ assert lines[0] == "Name Stmts Miss Branch BrPart Cover"
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[5], "1 file skipped due to complete coverage.")
+ assert squeezed[5] == "1 file skipped due to complete coverage."
def test_report_skip_covered_no_data(self):
report = self.report_from_command("coverage report --skip-covered")
# No data to report.
- self.assertEqual(self.line_count(report), 1, report)
+ assert self.line_count(report) == 1, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[0], "No data to report.")
+ assert squeezed[0] == "No data to report."
def test_report_skip_empty(self):
self.make_file("main.py", """
@@ -462,7 +464,7 @@ def normal():
self.make_file("submodule/__init__.py", "")
self.omit_site_packages()
out = self.run_command("coverage run main.py")
- self.assertEqual(out, "z\n")
+ assert out == "z\n"
report = self.report_from_command("coverage report --skip-empty")
# Name Stmts Miss Cover
@@ -473,18 +475,18 @@ def normal():
#
# 1 empty file skipped.
- self.assertEqual(self.line_count(report), 7, report)
+ assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[2], "main.py 4 0 100%")
- self.assertEqual(squeezed[4], "TOTAL 4 0 100%")
- self.assertEqual(squeezed[6], "1 empty file skipped.")
- self.assertEqual(self.last_command_status, 0)
+ assert squeezed[2] == "main.py 4 0 100%"
+ assert squeezed[4] == "TOTAL 4 0 100%"
+ assert squeezed[6] == "1 empty file skipped."
+ assert self.last_command_status == 0
def test_report_skip_empty_no_data(self):
self.make_file("__init__.py", "")
self.omit_site_packages()
out = self.run_command("coverage run __init__.py")
- self.assertEqual(out, "")
+ assert out == ""
report = self.report_from_command("coverage report --skip-empty")
# Name Stmts Miss Cover
@@ -492,10 +494,10 @@ def test_report_skip_empty_no_data(self):
#
# 1 empty file skipped.
- self.assertEqual(self.line_count(report), 6, report)
+ assert self.line_count(report) == 6, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[3], "TOTAL 0 0 100%")
- self.assertEqual(squeezed[5], "1 empty file skipped.")
+ assert squeezed[3] == "TOTAL 0 0 100%"
+ assert squeezed[5] == "1 empty file skipped."
def test_report_precision(self):
self.make_file(".coveragerc", """\
@@ -524,7 +526,7 @@ def foo():
foo()
""")
out = self.run_command("coverage run --branch main.py")
- self.assertEqual(out, "n\nz\n")
+ assert out == "n\nz\n"
report = self.report_from_command("coverage report")
# Name Stmts Miss Branch BrPart Cover
@@ -535,11 +537,11 @@ def foo():
# ------------------------------------------------------
# TOTAL 13 0 4 1 94.118%
- self.assertEqual(self.line_count(report), 7, report)
+ assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
- self.assertEqual(squeezed[2], "covered.py 3 0 0 0 100.000%")
- self.assertEqual(squeezed[4], "not_covered.py 4 0 2 1 83.333%")
- self.assertEqual(squeezed[6], "TOTAL 13 0 4 1 94.118%")
+ assert squeezed[2] == "covered.py 3 0 0 0 100.000%"
+ assert squeezed[4] == "not_covered.py 4 0 2 1 83.333%"
+ assert squeezed[6] == "TOTAL 13 0 4 1 94.118%"
def test_dotpy_not_python(self):
# We run a .py file, and when reporting, we can't parse it as Python.
@@ -560,15 +562,10 @@ def test_dotpy_not_python(self):
errmsg = re.sub(r"parse '.*mycode.py", "parse 'mycode.py", errmsg)
# The actual error message varies version to version
errmsg = re.sub(r": '.*' at", ": 'error' at", errmsg)
- self.assertEqual(
- "Couldn't parse 'mycode.py' as Python source: 'error' at line 1",
- errmsg,
- )
+ assert errmsg == "Couldn't parse 'mycode.py' as Python source: 'error' at line 1"
+ @pytest.mark.skipif(env.JYTHON, reason="Jython doesn't like accented file names")
def test_accenteddotpy_not_python(self):
- if env.JYTHON:
- self.skipTest("Jython doesn't like accented file names")
-
# We run a .py file with a non-ascii name, and when reporting, we can't
# parse it as Python. We should get an error message in the report.
@@ -590,7 +587,7 @@ def test_accenteddotpy_not_python(self):
expected = u"Couldn't parse 'accented\xe2.py' as Python source: 'error' at line 1"
if env.PY2:
expected = expected.encode(output_encoding())
- self.assertEqual(expected, errmsg)
+ assert expected == errmsg
def test_dotpy_not_python_ignored(self):
# We run a .py file, and when reporting, we can't parse it as Python,
@@ -606,9 +603,9 @@ def test_dotpy_not_python_ignored(self):
# ----------------------------
# No data to report.
- self.assertEqual(self.line_count(report), 4)
- self.assertIn('No data to report.', report)
- self.assertIn('(couldnt-parse)', report)
+ assert self.line_count(report) == 4
+ assert 'No data to report.' in report
+ assert '(couldnt-parse)' in report
def test_dothtml_not_python(self):
# We run a .html file, and when reporting, we can't parse it as
@@ -625,8 +622,8 @@ def test_dothtml_not_python(self):
# ----------------------------
# No data to report.
- self.assertEqual(self.line_count(report), 3)
- self.assertIn('No data to report.', report)
+ assert self.line_count(report) == 3
+ assert 'No data to report.' in report
def test_report_no_extension(self):
self.make_file("xxx", """\
@@ -640,9 +637,9 @@ def test_report_no_extension(self):
print("xxx: %r %r %r %r" % (a, b, c, d))
""")
out = self.run_command("coverage run --source=. xxx")
- self.assertEqual(out, "xxx: 3 4 0 7\n")
+ assert out == "xxx: 3 4 0 7\n"
report = self.report_from_command("coverage report")
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 7 1 86%")
+ assert self.last_line_squeezed(report) == "TOTAL 7 1 86%"
def test_report_with_chdir(self):
self.make_file("chdir.py", """\
@@ -654,9 +651,9 @@ def test_report_with_chdir(self):
""")
self.make_file("subdir/something", "hello")
out = self.run_command("coverage run --source=. chdir.py")
- self.assertEqual(out, "Line One\nLine Two\nhello\n")
+ assert out == "Line One\nLine Two\nhello\n"
report = self.report_from_command("coverage report")
- self.assertEqual(self.last_line_squeezed(report), "TOTAL 5 0 100%")
+ assert self.last_line_squeezed(report) == "TOTAL 5 0 100%"
def get_report(self, cov):
"""Get the report from `cov`, and canonicalize it."""
@@ -680,10 +677,10 @@ def branch(x):
""")
cov = coverage.Coverage(branch=True, source=["."])
cov.start()
- import main # pragma: nested # pylint: disable=unused-import
+ import main # pragma: nested # pylint: disable=unused-import, import-error
cov.stop() # pragma: nested
report = self.get_report(cov).splitlines()
- self.assertIn("mybranch.py 5 5 2 0 0%", report)
+ assert "mybranch.py 5 5 2 0 0%" in report
def run_TheCode_and_report_it(self):
"""A helper for the next few tests."""
@@ -699,21 +696,19 @@ def test_bug_203_mixed_case_listed_twice_with_rc(self):
report = self.run_TheCode_and_report_it()
- self.assertIn("TheCode", report)
- self.assertNotIn("thecode", report)
+ assert "TheCode" in report
+ assert "thecode" not in report
def test_bug_203_mixed_case_listed_twice(self):
self.make_file("TheCode.py", "a = 1\n")
report = self.run_TheCode_and_report_it()
- self.assertIn("TheCode", report)
- self.assertNotIn("thecode", report)
+ assert "TheCode" in report
+ assert "thecode" not in report
+ @pytest.mark.skipif(not env.WINDOWS, reason=".pyw files are only on Windows.")
def test_pyw_files(self):
- if not env.WINDOWS:
- self.skipTest(".pyw files are only on Windows.")
-
# https://github.com/nedbat/coveragepy/issues/261
self.make_file("start.pyw", """\
import mod
@@ -728,10 +723,10 @@ def test_pyw_files(self):
cov.stop() # pragma: nested
report = self.get_report(cov)
- self.assertNotIn("NoSource", report)
+ assert "NoSource" not in report
report = report.splitlines()
- self.assertIn("start.pyw 2 0 100%", report)
- self.assertIn("mod.pyw 1 0 100%", report)
+ assert "start.pyw 2 0 100%" in report
+ assert "mod.pyw 1 0 100%" in report
def test_tracing_pyc_file(self):
# Create two Python files.
@@ -744,17 +739,14 @@ def test_tracing_pyc_file(self):
# Run the program.
cov = coverage.Coverage()
cov.start()
- import main # pragma: nested # pylint: disable=unused-import
+ import main # pragma: nested # pylint: disable=unused-import, import-error
cov.stop() # pragma: nested
report = self.get_report(cov).splitlines()
- self.assertIn("mod.py 1 0 100%", report)
+ assert "mod.py 1 0 100%" in report
+ @pytest.mark.skipif(env.PYPY2, reason="PyPy2 doesn't run bare .pyc files")
def test_missing_py_file_during_run(self):
- # PyPy2 doesn't run bare .pyc files.
- if env.PYPY2:
- self.skipTest("PyPy2 doesn't run bare .pyc files")
-
# Create two Python files.
self.make_file("mod.py", "a = 1\n")
self.make_file("main.py", "import mod\n")
@@ -768,19 +760,19 @@ def test_missing_py_file_during_run(self):
# the source location though.
if env.PY3 and not env.JYTHON:
pycs = glob.glob("__pycache__/mod.*.pyc")
- self.assertEqual(len(pycs), 1)
+ assert len(pycs) == 1
os.rename(pycs[0], "mod.pyc")
# Run the program.
cov = coverage.Coverage()
cov.start()
- import main # pragma: nested # pylint: disable=unused-import
+ import main # pragma: nested # pylint: disable=unused-import, import-error
cov.stop() # pragma: nested
# Put back the missing Python file.
self.make_file("mod.py", "a = 1\n")
report = self.get_report(cov).splitlines()
- self.assertIn("mod.py 1 0 100%", report)
+ assert "mod.py 1 0 100%" in report
class SummaryTest2(UsingModulesMixin, CoverageTest):
@@ -804,8 +796,8 @@ def test_empty_files(self):
report = repout.getvalue().replace('\\', '/')
report = re.sub(r"\s+", " ", report)
- self.assertIn("tests/modules/pkg1/__init__.py 1 0 0 0 100%", report)
- self.assertIn("tests/modules/pkg2/__init__.py 0 0 0 0 100%", report)
+ assert "tests/modules/pkg1/__init__.py 1 0 0 0 100%" in report
+ assert "tests/modules/pkg2/__init__.py 0 0 0 0 100%" in report
class ReportingReturnValueTest(CoverageTest):
@@ -830,17 +822,17 @@ def run_coverage(self):
def test_report(self):
cov = self.run_coverage()
val = cov.report(include="*/doit.py")
- self.assertAlmostEqual(val, 85.7, 1)
+ assert round(abs(val-85.7), 1) == 0
def test_html(self):
cov = self.run_coverage()
val = cov.html_report(include="*/doit.py")
- self.assertAlmostEqual(val, 85.7, 1)
+ assert round(abs(val-85.7), 1) == 0
def test_xml(self):
cov = self.run_coverage()
val = cov.xml_report(include="*/doit.py")
- self.assertAlmostEqual(val, 85.7, 1)
+ assert round(abs(val-85.7), 1) == 0
class TestSummaryReporterConfiguration(CoverageTest):
@@ -896,37 +888,35 @@ def test_test_data(self):
# TOTAL 586 386 34%
lines = report.splitlines()[2:-2]
- self.assertEqual(len(lines), 3)
+ assert len(lines) == 3
nums = [list(map(int, l.replace('%', '').split()[1:])) for l in lines]
# [
# [339, 155, 54],
# [ 13, 3, 77],
# [234, 228, 3]
# ]
- self.assertTrue(nums[1][0] < nums[2][0] < nums[0][0])
- self.assertTrue(nums[1][1] < nums[0][1] < nums[2][1])
- self.assertTrue(nums[2][2] < nums[0][2] < nums[1][2])
+ assert nums[1][0] < nums[2][0] < nums[0][0]
+ assert nums[1][1] < nums[0][1] < nums[2][1]
+ assert nums[2][2] < nums[0][2] < nums[1][2]
def test_defaults(self):
"""Run the report with no configuration options."""
report = self.get_summary_text()
- self.assertNotIn('Missing', report)
- self.assertNotIn('Branch', report)
+ assert 'Missing' not in report
+ assert 'Branch' not in report
def test_print_missing(self):
"""Run the report printing the missing lines."""
report = self.get_summary_text(('report:show_missing', True))
- self.assertIn('Missing', report)
- self.assertNotIn('Branch', report)
+ assert 'Missing' in report
+ assert 'Branch' not in report
def assert_ordering(self, text, *words):
"""Assert that the `words` appear in order in `text`."""
indexes = list(map(text.find, words))
assert -1 not in indexes
- self.assertEqual(
- indexes, sorted(indexes),
- "The words %r don't appear in order in %r" % (words, text)
- )
+ msg = "The words %r don't appear in order in %r" % (words, text)
+ assert indexes == sorted(indexes), msg
def test_sort_report_by_stmts(self):
# Sort the text report by the Stmts column.
@@ -943,8 +933,18 @@ def test_sort_report_by_cover(self):
report = self.get_summary_text(('report:sort', 'Cover'))
self.assert_ordering(report, "file3.py", "file1.py", "file2.py")
+ def test_sort_report_by_cover_plus(self):
+ # Sort the text report by the Cover column, including the explicit + sign.
+ report = self.get_summary_text(('report:sort', '+Cover'))
+ self.assert_ordering(report, "file3.py", "file1.py", "file2.py")
+
+ def test_sort_report_by_cover_reversed(self):
+ # Sort the text report by the Cover column reversed.
+ report = self.get_summary_text(('report:sort', '-Cover'))
+ self.assert_ordering(report, "file2.py", "file1.py", "file3.py")
+
def test_sort_report_by_invalid_option(self):
# Sort the text report by a nonsense column.
msg = "Invalid sorting option: 'Xyzzy'"
- with self.assertRaisesRegex(CoverageException, msg):
+ with pytest.raises(CoverageException, match=msg):
self.get_summary_text(('report:sort', 'Xyzzy'))
diff --git a/tests/test_templite.py b/tests/test_templite.py
index 321db8307..770e97f97 100644
--- a/tests/test_templite.py
+++ b/tests/test_templite.py
@@ -6,6 +6,8 @@
import re
+import pytest
+
from coverage.templite import Templite, TempliteSyntaxError, TempliteValueError
from tests.coveragetest import CoverageTest
@@ -39,7 +41,7 @@ def try_render(self, text, ctx=None, result=None):
# If result is None, then an exception should have prevented us getting
# to here.
assert result is not None
- self.assertEqual(actual, result)
+ assert actual == result
def assertSynErr(self, msg):
"""Assert that a `TempliteSyntaxError` will happen.
@@ -48,15 +50,12 @@ def assertSynErr(self, msg):
"""
pat = "^" + re.escape(msg) + "$"
- return self.assertRaisesRegex(TempliteSyntaxError, pat)
+ return pytest.raises(TempliteSyntaxError, match=pat)
def test_passthrough(self):
# Strings without variables are passed through unchanged.
- self.assertEqual(Templite("Hello").render(), "Hello")
- self.assertEqual(
- Templite("Hello, 20% fun time!").render(),
- "Hello, 20% fun time!"
- )
+ assert Templite("Hello").render() == "Hello"
+ assert Templite("Hello, 20% fun time!").render() == "Hello, 20% fun time!"
def test_variables(self):
# Variables use {{var}} syntax.
@@ -64,7 +63,7 @@ def test_variables(self):
def test_undefined_variables(self):
# Using undefined names is an error.
- with self.assertRaisesRegex(Exception, "'name'"):
+ with pytest.raises(Exception, match="'name'"):
self.try_render("Hi, {{name}}!")
def test_pipes(self):
@@ -87,8 +86,8 @@ def test_reusability(self):
}
template = Templite("This is {{name|upper}}{{punct}}", globs)
- self.assertEqual(template.render({'name':'Ned'}), "This is NED!")
- self.assertEqual(template.render({'name':'Ben'}), "This is BEN!")
+ assert template.render({'name':'Ned'}) == "This is NED!"
+ assert template.render({'name':'Ben'}) == "This is BEN!"
def test_attribute(self):
# Variables' attributes can be accessed with dots.
@@ -298,7 +297,7 @@ def test_non_ascii(self):
def test_exception_during_evaluation(self):
# TypeError: Couldn't evaluate {{ foo.bar.baz }}:
regex = "^Couldn't evaluate None.bar$"
- with self.assertRaisesRegex(TempliteValueError, regex):
+ with pytest.raises(TempliteValueError, match=regex):
self.try_render(
"Hey {{foo.bar.baz}} there", {'foo': None}, "Hey ??? there"
)
diff --git a/tests/test_testing.py b/tests/test_testing.py
index 34ea32635..f5d9f9421 100644
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -8,20 +8,21 @@
import os
import re
import sys
+import unittest
import pytest
import coverage
from coverage import tomlconfig
-from coverage.backunittest import TestCase, unittest
from coverage.files import actual_path
from coverage.misc import StopEverything
-from tests.coveragetest import CoverageTest, convert_skip_exceptions
+from tests.coveragetest import CoverageTest
from tests.helpers import (
- arcs_to_arcz_repr, arcz_to_arcs,
+ arcs_to_arcz_repr, arcz_to_arcs, assert_count_equal,
CheckUniqueFilenames, re_lines, re_line, without_module,
)
+from tests.mixins import convert_skip_exceptions
def test_xdist_sys_path_nuttiness_is_fixed():
@@ -30,16 +31,13 @@ def test_xdist_sys_path_nuttiness_is_fixed():
assert os.environ.get('PYTHONPATH') is None
-class TestingTest(TestCase):
- """Tests of helper methods on `backunittest.TestCase`."""
-
- def test_assert_count_equal(self):
- self.assertCountEqual(set(), set())
- self.assertCountEqual({1,2,3}, {3,1,2})
- with self.assertRaises(AssertionError):
- self.assertCountEqual({1,2,3}, set())
- with self.assertRaises(AssertionError):
- self.assertCountEqual({1,2,3}, {4,5,6})
+def test_assert_count_equal():
+ assert_count_equal(set(), set())
+ assert_count_equal({"a": 1, "b": 2}, ["b", "a"])
+ with pytest.raises(AssertionError):
+ assert_count_equal({1,2,3}, set())
+ with pytest.raises(AssertionError):
+ assert_count_equal({1,2,3}, {4,5,6})
class CoverageTestTest(CoverageTest):
@@ -49,11 +47,11 @@ def test_file_exists(self):
self.make_file("whoville.txt", "We are here!")
self.assert_exists("whoville.txt")
self.assert_doesnt_exist("shadow.txt")
- msg = "False is not true : File 'whoville.txt' shouldn't exist"
- with self.assertRaisesRegex(AssertionError, msg):
+ msg = "File 'whoville.txt' shouldn't exist"
+ with pytest.raises(AssertionError, match=msg):
self.assert_doesnt_exist("whoville.txt")
- msg = "False is not true : File 'shadow.txt' should exist"
- with self.assertRaisesRegex(AssertionError, msg):
+ msg = "File 'shadow.txt' should exist"
+ with pytest.raises(AssertionError, match=msg):
self.assert_exists("shadow.txt")
def test_file_count(self):
@@ -65,27 +63,27 @@ def test_file_count(self):
self.assert_file_count("afile.*", 1)
self.assert_file_count("*.q", 0)
msg = re.escape(
- "3 != 13 : There should be 13 files matching 'a*.txt', but there are these: "
+ "There should be 13 files matching 'a*.txt', but there are these: "
"['abcde.txt', 'afile.txt', 'axczz.txt']"
)
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.assert_file_count("a*.txt", 13)
msg = re.escape(
- "2 != 12 : There should be 12 files matching '*c*.txt', but there are these: "
+ "There should be 12 files matching '*c*.txt', but there are these: "
"['abcde.txt', 'axczz.txt']"
)
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.assert_file_count("*c*.txt", 12)
msg = re.escape(
- "1 != 11 : There should be 11 files matching 'afile.*', but there are these: "
+ "There should be 11 files matching 'afile.*', but there are these: "
"['afile.txt']"
)
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.assert_file_count("afile.*", 11)
msg = re.escape(
- "0 != 10 : There should be 10 files matching '*.q', but there are these: []"
+ "There should be 10 files matching '*.q', but there are these: []"
)
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.assert_file_count("*.q", 10)
def test_assert_startwith(self):
@@ -93,10 +91,10 @@ def test_assert_startwith(self):
self.assert_starts_with("xyz\nabc", "xy")
self.assert_starts_with("xyzzy", ("x", "z"))
msg = re.escape("'xyz' doesn't start with 'a'")
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.assert_starts_with("xyz", "a")
msg = re.escape("'xyz\\nabc' doesn't start with 'a'")
- with self.assertRaisesRegex(AssertionError, msg):
+ with pytest.raises(AssertionError, match=msg):
self.assert_starts_with("xyz\nabc", "a")
def test_assert_recent_datetime(self):
@@ -107,17 +105,17 @@ def now_delta(seconds):
# Default delta is 10 seconds.
self.assert_recent_datetime(now_delta(0))
self.assert_recent_datetime(now_delta(-9))
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.assert_recent_datetime(now_delta(-11))
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.assert_recent_datetime(now_delta(1))
# Delta is settable.
self.assert_recent_datetime(now_delta(0), seconds=120)
self.assert_recent_datetime(now_delta(-100), seconds=120)
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.assert_recent_datetime(now_delta(-1000), seconds=120)
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.assert_recent_datetime(now_delta(1), seconds=120)
def test_assert_warnings(self):
@@ -143,13 +141,13 @@ def test_assert_warnings(self):
# But if there are a bunch of expected warnings, they have to all happen.
warn_regex = r"Didn't find warning 'You' in \['Hello there!'\]"
- with self.assertRaisesRegex(AssertionError, warn_regex):
+ with pytest.raises(AssertionError, match=warn_regex):
with self.assert_warnings(cov, ["Hello.*!", "You"]):
cov._warn("Hello there!")
# Make a different warning than expected, it should raise an assertion.
warn_regex = r"Didn't find warning 'Not me' in \['Hello there!'\]"
- with self.assertRaisesRegex(AssertionError, warn_regex):
+ with pytest.raises(AssertionError, match=warn_regex):
with self.assert_warnings(cov, ["Not me"]):
cov._warn("Hello there!")
@@ -159,13 +157,13 @@ def test_assert_warnings(self):
# But it should fail if the unexpected warning does appear.
warn_regex = r"Found warning 'Bye' in \['Hi', 'Bye'\]"
- with self.assertRaisesRegex(AssertionError, warn_regex):
+ with pytest.raises(AssertionError, match=warn_regex):
with self.assert_warnings(cov, ["Hi"], not_warnings=["Bye"]):
cov._warn("Hi")
cov._warn("Bye")
# assert_warnings shouldn't hide a real exception.
- with self.assertRaisesRegex(ZeroDivisionError, "oops"):
+ with pytest.raises(ZeroDivisionError, match="oops"):
with self.assert_warnings(cov, ["Hello there!"]):
raise ZeroDivisionError("oops")
@@ -178,7 +176,7 @@ def test_assert_no_warnings(self):
# If you said there would be no warnings, and there were, fail!
warn_regex = r"Unexpected warnings: \['Watch out!'\]"
- with self.assertRaisesRegex(AssertionError, warn_regex):
+ with pytest.raises(AssertionError, match=warn_regex):
with self.assert_warnings(cov, []):
cov._warn("Watch out!")
@@ -192,21 +190,21 @@ def test_sub_python_is_this_python(self):
print(os.environ['COV_FOOBAR'])
""")
out = self.run_command("python showme.py").splitlines()
- self.assertEqual(actual_path(out[0]), actual_path(sys.executable))
- self.assertEqual(out[1], os.__file__)
- self.assertEqual(out[2], 'XYZZY')
+ assert actual_path(out[0]) == actual_path(sys.executable)
+ assert out[1] == os.__file__
+ assert out[2] == 'XYZZY'
# Try it with a "coverage debug sys" command.
out = self.run_command("coverage debug sys")
executable = re_line(out, "executable:")
executable = executable.split(":", 1)[1].strip()
- self.assertTrue(_same_python_executable(executable, sys.executable))
+ assert _same_python_executable(executable, sys.executable)
# "environment: COV_FOOBAR = XYZZY" or "COV_FOOBAR = XYZZY"
environ = re_line(out, "COV_FOOBAR")
_, _, environ = environ.rpartition(":")
- self.assertEqual(environ.strip(), "COV_FOOBAR = XYZZY")
+ assert environ.strip() == "COV_FOOBAR = XYZZY"
def test_run_command_stdout_stderr(self):
# run_command should give us both stdout and stderr.
@@ -216,8 +214,8 @@ def test_run_command_stdout_stderr(self):
print("StdOut")
""")
out = self.run_command("python outputs.py")
- self.assertIn("StdOut\n", out)
- self.assertIn("StdErr\n", out)
+ assert "StdOut\n" in out
+ assert "StdErr\n" in out
class CheckUniqueFilenamesTest(CoverageTest):
@@ -243,10 +241,61 @@ def test_detect_duplicate(self):
assert stub.method("file2", 1723, b="what") == (23, "file2", 1723, "what")
# A duplicate file name trips an assertion.
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
stub.method("file1")
+class CheckCoverageTest(CoverageTest):
+ """Tests of the failure assertions in check_coverage."""
+
+ CODE = """\
+ a, b = 1, 1
+ def oops(x):
+ if x % 2:
+ raise Exception("odd")
+ try:
+ a = 6
+ oops(1)
+ a = 8
+ except:
+ b = 10
+ assert a == 6 and b == 10
+ """
+ ARCZ = ".1 12 -23 34 3-2 4-2 25 56 67 78 8B 9A AB B."
+ ARCZ_MISSING = "3-2 78 8B"
+ ARCZ_UNPREDICTED = "79"
+
+ def test_check_coverage_possible(self):
+ msg = r"(?s)Possible arcs differ: .*- \(6, 3\).*\+ \(6, 7\)"
+ with pytest.raises(AssertionError, match=msg):
+ self.check_coverage(
+ self.CODE,
+ arcz=self.ARCZ.replace("7", "3"),
+ arcz_missing=self.ARCZ_MISSING,
+ arcz_unpredicted=self.ARCZ_UNPREDICTED,
+ )
+
+ def test_check_coverage_missing(self):
+ msg = r"(?s)Missing arcs differ: .*- \(3, 8\).*\+ \(7, 8\)"
+ with pytest.raises(AssertionError, match=msg):
+ self.check_coverage(
+ self.CODE,
+ arcz=self.ARCZ,
+ arcz_missing=self.ARCZ_MISSING.replace("7", "3"),
+ arcz_unpredicted=self.ARCZ_UNPREDICTED,
+ )
+
+ def test_check_coverage_unpredicted(self):
+ msg = r"(?s)Unpredicted arcs differ: .*- \(3, 9\).*\+ \(7, 9\)"
+ with pytest.raises(AssertionError, match=msg):
+ self.check_coverage(
+ self.CODE,
+ arcz=self.ARCZ,
+ arcz_missing=self.ARCZ_MISSING,
+ arcz_unpredicted=self.ARCZ_UNPREDICTED.replace("7", "3")
+ )
+
+
@pytest.mark.parametrize("text, pat, result", [
("line1\nline2\nline3\n", "line", "line1\nline2\nline3\n"),
("line1\nline2\nline3\n", "[13]", "line1\nline3\n"),
diff --git a/tests/test_version.py b/tests/test_version.py
index 11b180d52..00d65624f 100644
--- a/tests/test_version.py
+++ b/tests/test_version.py
@@ -16,24 +16,19 @@ class VersionTest(CoverageTest):
def test_version_info(self):
# Make sure we didn't screw up the version_info tuple.
- self.assertIsInstance(coverage.version_info, tuple)
- self.assertEqual([type(d) for d in coverage.version_info], [int, int, int, str, int])
- self.assertIn(coverage.version_info[3], ['alpha', 'beta', 'candidate', 'final'])
+ assert isinstance(coverage.version_info, tuple)
+ assert [type(d) for d in coverage.version_info] == [int, int, int, str, int]
+ assert coverage.version_info[3] in ['alpha', 'beta', 'candidate', 'final']
def test_make_version(self):
- self.assertEqual(_make_version(4, 0, 0, 'alpha', 0), "4.0a0")
- self.assertEqual(_make_version(4, 0, 0, 'alpha', 1), "4.0a1")
- self.assertEqual(_make_version(4, 0, 0, 'final', 0), "4.0")
- self.assertEqual(_make_version(4, 1, 2, 'beta', 3), "4.1.2b3")
- self.assertEqual(_make_version(4, 1, 2, 'final', 0), "4.1.2")
- self.assertEqual(_make_version(5, 10, 2, 'candidate', 7), "5.10.2rc7")
+ assert _make_version(4, 0, 0, 'alpha', 0) == "4.0a0"
+ assert _make_version(4, 0, 0, 'alpha', 1) == "4.0a1"
+ assert _make_version(4, 0, 0, 'final', 0) == "4.0"
+ assert _make_version(4, 1, 2, 'beta', 3) == "4.1.2b3"
+ assert _make_version(4, 1, 2, 'final', 0) == "4.1.2"
+ assert _make_version(5, 10, 2, 'candidate', 7) == "5.10.2rc7"
def test_make_url(self):
- self.assertEqual(
- _make_url(4, 0, 0, 'final', 0),
- "https://coverage.readthedocs.io"
- )
- self.assertEqual(
- _make_url(4, 1, 2, 'beta', 3),
- "https://coverage.readthedocs.io/en/coverage-4.1.2b3"
- )
+ assert _make_url(4, 0, 0, 'final', 0) == "https://coverage.readthedocs.io"
+ expected = "https://coverage.readthedocs.io/en/coverage-4.1.2b3"
+ assert _make_url(4, 1, 2, 'beta', 3) == expected
diff --git a/tests/test_xml.py b/tests/test_xml.py
index e3be7a543..13e015d6b 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -9,6 +9,7 @@
import re
from xml.etree import ElementTree
+import pytest
from unittest_mixins import change_dir
import coverage
@@ -87,11 +88,11 @@ def test_assert_source(self):
self.assert_source(dom, "something")
self.assert_source(dom, "another")
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.assert_source(dom, "hello")
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.assert_source(dom, "foo")
- with self.assertRaises(AssertionError):
+ with pytest.raises(AssertionError):
self.assert_source(dom, "thing")
@@ -274,10 +275,7 @@ def package_and_class_tags(self, cov):
def assert_package_and_class_tags(self, cov, result):
"""Check the XML package and class tags from `cov` match `result`."""
- self.assertEqual(
- unbackslash(list(self.package_and_class_tags(cov))),
- unbackslash(result),
- )
+ assert unbackslash(list(self.package_and_class_tags(cov))) == unbackslash(result)
def test_package_names(self):
self.make_tree(width=1, depth=3)
diff --git a/tox.ini b/tox.ini
index 317dea42f..6eeee5bc0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,6 +2,7 @@
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
[tox]
+# When changing this list, be sure to check the [gh-actions] list below.
envlist = py{27,35,36,37,38,39,310}, pypy{2,3}, doc, lint
skip_missing_interpreters = {env:COVERAGE_SKIP_MISSING_INTERPRETERS:True}
toxworkdir = {env:TOXWORKDIR:.tox}
@@ -93,5 +94,6 @@ python =
3.7: py37
3.8: py38
3.9: py39
+ 3.10: py310
pypy: pypy
pypy3: pypy3