From f17bd4f01a4377d2c71dffb1999d79eba0c0b2ee Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 26 Jan 2021 06:38:53 -0500 Subject: [PATCH 01/67] Combine kit download with kit check --- Makefile | 5 ++++- howto.txt | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index ff5d3c999..4bc811e21 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 diff --git a/howto.txt b/howto.txt index 8a912833d..6ecea7cf3 100644 --- a/howto.txt +++ b/howto.txt @@ -46,11 +46,9 @@ - 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 + - 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: From b04613741e1d4d113a5bed54418d730bf053fcc2 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 26 Jan 2021 06:40:13 -0500 Subject: [PATCH 02/67] Version bump --- CHANGES.rst | 6 ++++++ coverage/version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 98f632842..292217b5e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,6 +21,12 @@ want to know what's different in 5.0 since 4.5.x, see :ref:`whatsnew5x`. .. Version 9.8.1 --- 2027-07-27 .. ---------------------------- +Unreleased +---------- + +Nothing yet. + + .. _changes_54: Version 5.4 --- 2021-01-24 diff --git a/coverage/version.py b/coverage/version.py index 8cc58dfb1..6cd18980d 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, 4, 1, "alpha", 0) def _make_version(major, minor, micro, releaselevel, serial): From 782fb4036b26941d200e66a45c0fad32f9d34347 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 26 Jan 2021 06:45:25 -0500 Subject: [PATCH 03/67] Add versionadded info for the new 5.4 configuration options --- doc/config.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/config.rst b/doc/config.rst index 3a8b0784d..e5c70b5a7 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -353,12 +353,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. From 74a9aac92440e439f10b3fee67f2890cf059e7ff Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Tue, 26 Jan 2021 17:50:06 +0100 Subject: [PATCH 04/67] fix typos in changelog Signed-off-by: Valentin Lab --- CHANGES.rst | 4 ++-- CONTRIBUTORS.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 292217b5e..8a27e5ee3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -43,8 +43,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..80084213f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -136,7 +136,7 @@ Ted Wexler Thijs Triemstra Thomas Grainger Titus Brown -Valentine Lab +Valentin Lab Vince Salvino Ville Skyttä Xie Yanbo From c25660176f90b466abd403a50a03962bcb5e913a Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 26 Jan 2021 19:47:02 -0500 Subject: [PATCH 05/67] Update the doc requirements --- doc/requirements.pip | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/requirements.pip b/doc/requirements.pip index eea4c8f99..bb875ca4e 100644 --- a/doc/requirements.pip +++ b/doc/requirements.pip @@ -4,9 +4,12 @@ doc8==0.8.1 pyenchant==3.2.0 -sphinx==3.3.1 -sphinx-rst-builder==0.0.3 +sphinx==3.4.3 +#sphinx-rst-builder==0.0.3 +# fails, +# fixed with https://github.com/davidfritzsche/sphinx-rst-builder/pull/3 +git+https://github.com/nedbat/sphinx-rst-builder 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 From a559e7cc2cd4de6095aa84347e36a7c526cd6147 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 27 Jan 2021 07:32:38 -0500 Subject: [PATCH 06/67] refactor: Move post-processing into CoverageConfig --- coverage/config.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/coverage/config.py b/coverage/config.py index 803dcd5d7..026f8645e 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -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 From 0143891b04c0c800fe1a508ab424cbe825f4210b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 27 Jan 2021 18:50:46 -0500 Subject: [PATCH 07/67] chore: update pylint (etc) versions Also: suppress some Python 3-only suggestions until later. --- pylintrc | 2 ++ requirements/dev.pip | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pylintrc b/pylintrc index d250e9b92..cfe67f966 100644 --- a/pylintrc +++ b/pylintrc @@ -83,6 +83,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/dev.pip b/requirements/dev.pip index 2cd0fe0e3..321dd156d 100644 --- a/requirements/dev.pip +++ b/requirements/dev.pip @@ -14,11 +14,11 @@ pluggy==0.13.1 # for linting. greenlet==0.4.16 -pylint==2.5.3 -check-manifest==0.42 +pylint==2.6.0 +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 From a0f6692f5cc9344ae790300dcc0cf743ac9abbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Larivi=C3=A8re?= Date: Sat, 30 Jan 2021 17:55:11 -0500 Subject: [PATCH 08/67] Add combine --keep (#1110) * Add combine --keep Related to https://github.com/nedbat/coveragepy/issues/1108 * Fix unit tests * Fix line too long * Fix line too long --- coverage/cmdline.py | 8 +++++++- coverage/control.py | 7 +++++-- coverage/data.py | 11 ++++++----- doc/help/combine.rst | 1 + doc/python-coverage.1.txt | 4 ++++ tests/test_api.py | 2 +- tests/test_cmdline.py | 10 +++++----- tests/test_process.py | 22 ++++++++++++++++++++++ 8 files changed, 51 insertions(+), 14 deletions(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 9c9ae868a..11bc5d992 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 combined 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 diff --git a/coverage/control.py b/coverage/control.py index 8d129bcb5..c952afcd5 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,6 +674,8 @@ 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 combined data files won't be deleted. + .. versionadded:: 4.0 The `data_paths` parameter. @@ -694,7 +696,8 @@ 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/doc/help/combine.rst b/doc/help/combine.rst index 35180cdde..c35d5b932 100644 --- a/doc/help/combine.rst +++ b/doc/help/combine.rst @@ -13,6 +13,7 @@ Options: -a, --append Append coverage data to .coverage, otherwise it starts clean each time. + --keep Keep combined 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/python-coverage.1.txt b/doc/python-coverage.1.txt index 0bbd44d0a..10a8927a9 100644 --- a/doc/python-coverage.1.txt +++ b/doc/python-coverage.1.txt @@ -109,6 +109,7 @@ 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. + Unless --keep is provided the combined 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 combined coverage file. + **debug** `TOPIC` ... Display information about the internals of coverage.py, for diagnosing diff --git a/tests/test_api.py b/tests/test_api.py index f8b7b4b2c..62fd9ebca 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -442,7 +442,7 @@ def test_combining_twice(self): cov2 = coverage.Coverage() with self.assertRaisesRegex(CoverageException, r"No data to combine"): - cov2.combine(strict=True) + cov2.combine(strict=True, keep=False) cov3 = coverage.Coverage() cov3.combine() diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 374adb0de..3b11881b0 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -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() """) diff --git a/tests/test_process.py b/tests/test_process.py index e48861568..b44147a39 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -244,6 +244,28 @@ def test_combine_parallel_data_no_append(self): data.read() self.assertEqual(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") + self.assertEqual(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') + 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() From f1050f902ae7a9d99a4ddaa9fb0069a9719b8c4f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 30 Jan 2021 18:13:47 -0500 Subject: [PATCH 09/67] doc: touch-ups for `combine --keep` --- CHANGES.rst | 7 ++++++- CONTRIBUTORS.txt | 3 ++- coverage/cmdline.py | 2 +- coverage/control.py | 4 +++- doc/cmd.rst | 3 +++ doc/help/combine.rst | 2 +- doc/python-coverage.1.txt | 6 +++--- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8a27e5ee3..0c5d76677 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,8 +24,13 @@ want to know what's different in 5.0 since 4.5.x, see :ref:`whatsnew5x`. Unreleased ---------- -Nothing yet. +- ``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. +.. _issue 1108: https://github.com/nedbat/coveragepy/issues/1108 +.. _pull request 1110: https://github.com/nedbat/coveragepy/pull/1110 .. _changes_54: diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 80084213f..b43eeaf82 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -54,9 +54,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 diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 11bc5d992..cdcde4510 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -33,7 +33,7 @@ class Opts(object): ) keep = optparse.make_option( '', '--keep', action='store_true', - help="Keep combined coverage files, otherwise they are deleted.", + help="Keep original coverage files, otherwise they are deleted.", ) branch = optparse.make_option( '', '--branch', action='store_true', diff --git a/coverage/control.py b/coverage/control.py index c952afcd5..358e4a977 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -674,7 +674,7 @@ def combine(self, data_paths=None, strict=False, keep=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 combined data files won't be deleted. + If `keep` is true, then original input data files won't be deleted. .. versionadded:: 4.0 The `data_paths` parameter. @@ -682,6 +682,8 @@ def combine(self, data_paths=None, strict=False, keep=False): .. versionadded:: 4.3 The `strict` parameter. + .. versionadded: 5.5 + The `keep` parameter. """ self._init() self._init_data(suffix=None) 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/help/combine.rst b/doc/help/combine.rst index c35d5b932..7926e9cd6 100644 --- a/doc/help/combine.rst +++ b/doc/help/combine.rst @@ -13,7 +13,7 @@ Options: -a, --append Append coverage data to .coverage, otherwise it starts clean each time. - --keep Keep combined coverage files, otherwise they are deleted. + --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/python-coverage.1.txt b/doc/python-coverage.1.txt index 10a8927a9..00c243de6 100644 --- a/doc/python-coverage.1.txt +++ b/doc/python-coverage.1.txt @@ -108,8 +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. - Unless --keep is provided the combined coverage files are deleted. + 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. @@ -121,7 +121,7 @@ COMMAND REFERENCE time. \--keep - Keep combined coverage file. + Keep original coverage data files. **debug** `TOPIC` ... From 42b066de2aaa65c92790b71e699d975c1f3c4309 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 30 Jan 2021 18:14:08 -0500 Subject: [PATCH 10/67] style: nicer long function call --- coverage/control.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/coverage/control.py b/coverage/control.py index 358e4a977..1623b0932 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -698,8 +698,13 @@ def combine(self, data_paths=None, strict=False, keep=False): for pattern in paths[1:]: aliases.add(pattern, result) - combine_parallel_data(self._data, - aliases=aliases, data_paths=data_paths, strict=strict, keep=keep) + combine_parallel_data( + self._data, + aliases=aliases, + data_paths=data_paths, + strict=strict, + keep=keep, + ) def get_data(self): """Get the collected data. From 814023c72082c75684259b7980f7b3465785b110 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 30 Jan 2021 18:14:40 -0500 Subject: [PATCH 11/67] build: next version will be 5.5 Since we've added a feature (combine --keep). --- coverage/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage/version.py b/coverage/version.py index 6cd18980d..eb4e36b1b 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, 1, "alpha", 0) +version_info = (5, 5, 0, "alpha", 0) def _make_version(major, minor, micro, releaselevel, serial): From 4fc64a97ce779c2d6bb972f0003b9b9f00e62c3a Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 30 Jan 2021 18:19:57 -0500 Subject: [PATCH 12/67] build: clearly label auto-generated files --- Makefile | 2 ++ doc/help/annotate.rst | 2 ++ doc/help/combine.rst | 2 ++ doc/help/debug.rst | 2 ++ doc/help/erase.rst | 2 ++ doc/help/html.rst | 2 ++ doc/help/json.rst | 2 ++ doc/help/report.rst | 2 ++ doc/help/run.rst | 2 ++ doc/help/xml.rst | 2 ++ 10 files changed, 20 insertions(+) diff --git a/Makefile b/Makefile index 4bc811e21..d7bc15b7d 100644 --- a/Makefile +++ b/Makefile @@ -121,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/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 7926e9cd6..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 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 From 843de4ea235e7eee3ff24a39a2f8b14da9ef0db0 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 07:16:56 -0500 Subject: [PATCH 13/67] refactor: unittest2pytest -w tests One step of moving to pure pytest tests. --- tests/coveragetest.py | 24 +-- tests/test_api.py | 201 +++++++++++----------- tests/test_arcs.py | 20 +-- tests/test_backward.py | 22 +-- tests/test_cmdline.py | 104 ++++++------ tests/test_collector.py | 4 +- tests/test_concurrency.py | 31 ++-- tests/test_config.py | 261 ++++++++++++++--------------- tests/test_context.py | 34 ++-- tests/test_coverage.py | 21 +-- tests/test_data.py | 159 +++++++++--------- tests/test_debug.py | 55 +++--- tests/test_execfile.py | 81 ++++----- tests/test_filereporter.py | 52 +++--- tests/test_files.py | 53 +++--- tests/test_html.py | 82 ++++----- tests/test_misc.py | 14 +- tests/test_numbits.py | 26 ++- tests/test_oddball.py | 59 +++---- tests/test_parser.py | 169 +++++++------------ tests/test_phystokens.py | 49 +++--- tests/test_plugins.py | 153 +++++++++-------- tests/test_process.py | 332 ++++++++++++++++++------------------- tests/test_python.py | 2 +- tests/test_results.py | 48 +++--- tests/test_setup.py | 22 +-- tests/test_summary.py | 255 ++++++++++++++-------------- tests/test_templite.py | 19 +-- tests/test_testing.py | 68 ++++---- tests/test_version.py | 26 ++- tests/test_xml.py | 13 +- 31 files changed, 1165 insertions(+), 1294 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index dbadd226b..ed3f18397 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -139,7 +139,7 @@ def assert_equal_arcs(self, a1, a2, msg=None): # 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) + assert s1 == s2, msg def check_coverage( self, text, lines=None, missing="", report="", @@ -198,7 +198,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 else: # lines is a list of possible line number lists, one of them # must match. @@ -210,7 +210,7 @@ def check_coverage( missing_formatted = analysis.missing_formatted() if isinstance(missing, string_class): - self.assertEqual(missing_formatted, missing) + assert missing_formatted == missing else: for missing_list in missing: if missing_formatted == missing_list: @@ -244,7 +244,7 @@ def check_coverage( 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 return cov @@ -313,19 +313,19 @@ def assert_same_files(self, flist1, flist2): 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 +335,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 +351,7 @@ def command_line(self, args, ret=OK): """ ret_actual = command_line(args) - self.assertEqual(ret_actual, ret) + assert 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, @@ -454,13 +454,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/test_api.py b/tests/test_api.py index 62fd9ebca..955599865 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,6 +22,7 @@ from coverage.misc import CoverageException from tests.coveragetest import CoverageTest, CoverageTestMethodsMixin, TESTS_DIR, UsingModulesMixin +import pytest class ApiTest(CoverageTest): @@ -63,8 +64,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 +83,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 +98,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 +116,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 == 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 +132,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 +151,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 +291,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 +309,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 +318,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 +335,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 +440,18 @@ def test_combining_twice(self): self.assert_exists(".coverage") cov2 = coverage.Coverage() - with self.assertRaisesRegex(CoverageException, r"No data to combine"): + 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, @@ -522,15 +521,15 @@ def test_warnings(self): cov.get_data() out = self.stdout() - self.assertIn("Hello\n", out) + assert "Hello\n" in out err = self.stderr() - self.assertIn(textwrap.dedent("""\ + 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", """\ @@ -546,15 +545,13 @@ def test_warnings_suppressed(self): cov.get_data() out = self.stdout() - self.assertIn("Hello\n", out) + assert "Hello\n" in out err = self.stderr() - self.assertIn( - "Coverage.py warning: Module sys has no Python source. (module-not-python)", + assert "Coverage.py warning: Module sys has no Python source. (module-not-python)" in \ err - ) - self.assertNotIn("module-not-imported", err) - self.assertNotIn("no-data-collected", 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 +559,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 +589,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 +634,16 @@ def test_switch_context_testrunner(self): # Labeled data is collected data = cov.get_data() - self.assertEqual( - [u'', u'multiply_six', u'multiply_zero'], + 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 +673,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()), - ) + assert [u'mysuite', u'mysuite|multiply_six', u'mysuite|multiply_zero'] == \ + 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 +691,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(), + assert self.stderr() == \ "Coverage.py warning: Conflicting dynamic contexts (dynamic-conflict)\n" - ) 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 +714,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): @@ -791,7 +782,7 @@ def test_explicit_namespace_module(self): 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): @@ -815,12 +806,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 +883,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 +921,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 +945,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 +1003,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 +1040,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 +1085,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 +1108,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") == False cov.report(show_missing=True) - self.assertEqual(cov.get_option("report:show_missing"), False) + assert cov.get_option("report:show_missing") == False class RelativePathTest(CoverageTest): @@ -1140,7 +1131,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..79280e2d6 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -322,7 +322,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 +332,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,7 +1086,7 @@ 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" def test_yield_from(self): if not env.PYBEHAVIOR.yield_from: @@ -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()[-1] == str(n) 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), + assert 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), + assert fr.missing_arc_description(4, -4, arcs_executed) == \ "line 4 didn't run the generator expression on line 4" - ) class DecoratorArcTest(CoverageTest): @@ -1620,7 +1616,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 +1653,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("""\ diff --git a/tests/test_backward.py b/tests/test_backward.py index 8acb8707c..01cc8dac0 100644 --- a/tests/test_backward.py +++ b/tests/test_backward.py @@ -5,18 +5,14 @@ from coverage.backunittest import TestCase from coverage.backward import iitems, binary_bytes, bytes_to_ints - - -class BackwardTest(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) +"""Tests of things from backward.py.""" +deftest_iitems(self): + d = {'a': 1, 'b': 2, 'c': 3} + items = [('a', 1), ('b', 2), ('c', 3)] + self.assertCountEqual(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))) + byte_values = [0, 255, 17, 23, 42, 57] + bb = binary_bytes(byte_values) + 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 3b11881b0..2c24c5988 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") @@ -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.", + assert "Options affecting multiprocessing must only be specified in a configuration file." in \ self.stderr() - ) - self.assertIn( - "Remove --branch from the command line.", + assert "Remove --branch from the command line." in \ self.stderr() - ) 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,18 +803,18 @@ 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 def test_help_contains_command_name(self): # Command name should be present in help output. @@ -830,7 +826,7 @@ def test_help_contains_command_name(self): 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_contains_command_name_from_package(self): # Command package name should be present in help output. @@ -847,38 +843,38 @@ def test_help_contains_command_name_from_package(self): 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 +910,25 @@ 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(), "") + assert ret == 1 + assert 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 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..cc9622d1d 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -20,6 +20,7 @@ from tests.coveragetest import CoverageTest from tests.helpers import remove_files +import re # These libraries aren't always available, we'll skip tests if they aren't. @@ -91,7 +92,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 +228,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 +249,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) @@ -399,17 +400,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 +460,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 +491,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 +511,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..0a742f3dd 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,6 +13,7 @@ from tests.coveragetest import CoverageTest, UsingModulesMixin from tests.helpers import without_module +import pytest class ConfigTest(CoverageTest): @@ -21,17 +22,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 +43,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 +56,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 +80,16 @@ 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"), + 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,10 @@ 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, + assert cov.config.data_file == "hello-world.fooey" + assert cov.config.branch == 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 +255,10 @@ 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, + assert cov.config.data_file == "hello-world.fooey" + assert cov.config.branch == 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 +291,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 +324,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 +358,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 +384,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 +398,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 +407,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 +416,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,7 +425,7 @@ 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): @@ -546,49 +541,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 == True + assert cov.config.json_pretty_print == True def test_config_file_settings(self): self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section="")) @@ -633,9 +628,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 == None + assert cov.config.branch == False def test_setupcfg_only_if_not_coveragerc(self): self.check_other_not_read_if_coveragerc("setup.cfg") @@ -651,8 +646,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 == None + assert cov.config.branch == False def test_setupcfg_only_if_prefixed(self): self.check_other_config_need_prefixes("setup.cfg") @@ -689,8 +684,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 +696,20 @@ 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_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 +717,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 +729,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 +742,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..be478760c 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -64,7 +64,7 @@ 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']) @@ -75,7 +75,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,7 +89,7 @@ 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']) @@ -100,7 +100,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 +110,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', []) @@ -251,40 +251,38 @@ 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" 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() == None diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 68eea1150..6529aa726 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -9,6 +9,7 @@ from coverage.misc import CoverageException from tests.coveragetest import CoverageTest +import pytest class TestCoverageTest(CoverageTest): @@ -50,7 +51,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 +60,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 +68,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 +79,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 +91,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" @@ -1844,7 +1845,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 +1858,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..d1ed04e12 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -19,6 +19,7 @@ from coverage.misc import CoverageException from tests.coveragetest import CoverageTest +import pytest LINES_1 = { @@ -77,7 +78,7 @@ 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`.""" @@ -88,7 +89,7 @@ def assert_lines1_data(self, covdata): 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 not covdata.has_arcs() def assert_arcs3_data(self, covdata): """Check that `covdata` has the data from ARCS3.""" @@ -98,7 +99,7 @@ def assert_arcs3_data(self, covdata): 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 covdata.has_arcs() class CoverageDataTest(DataTestHelpers, CoverageTest): @@ -108,27 +109,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 +158,13 @@ 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"): + with pytest.raises(CoverageException, match="Can't add arcs to existing line data"): 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"): + with pytest.raises(CoverageException, match="Can't add lines to existing arc data"): covdata.add_lines(LINES_1) def test_touch_file_with_lines(self): @@ -183,34 +184,33 @@ 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 +218,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 +226,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']}) + assert covdata.contexts_by_lineno('x.py') == \ + {-1: ['test_x'], 1: ['test_x'], 2: ['test_x'], 3: ['test_x']} 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 +266,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 +286,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 +324,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 +358,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 +373,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 +389,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 +416,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") == None self.assert_measured_files(covdata, []) def test_add_to_hash_with_lines(self): @@ -426,10 +424,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 +435,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 +446,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 +458,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 +525,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,12 +587,9 @@ def test_debug_output_with_debug_option(self): covdata2.read() self.assert_line_counts(covdata2, SUMMARY_1) - self.assertRegex( - debug.get_output(), - r"^Erasing data file '.*\.coverage'\n" - r"Creating data file '.*\.coverage'\n" - r"Opening data file '.*\.coverage'\n$" - ) + assert re.search(r"^Erasing data file '.*\.coverage'\n" \ + r"Creating data file '.*\.coverage'\n" \ + r"Opening data file '.*\.coverage'\n$", debug.get_output()) def test_debug_output_without_debug_option(self): # With a debug object, but not the dataio option, we don't get debug @@ -608,7 +603,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 +622,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 +630,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 +713,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 +773,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 +812,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..629665f96 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -15,6 +15,7 @@ from tests.coveragetest import CoverageTest from tests.helpers import re_line, re_lines +import re class InfoFormatterTest(CoverageTest): @@ -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,9 @@ 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, - ) + assert len(re_lines(out_lines, label_pat).splitlines()) == \ + 1, \ + "Incorrect lines for %r" % label def test_debug_sys(self): out_lines = self.f1_debug_output(["sys"]) @@ -191,11 +190,9 @@ 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, - ) + assert len(re_lines(out_lines, label_pat).splitlines()) == \ + 1, \ + "Incorrect lines for %r" % label def test_debug_sys_ctracer(self): out_lines = self.f1_debug_output(["sys"]) @@ -204,7 +201,7 @@ def test_debug_sys_ctracer(self): expected = "CTracer: available" else: expected = "CTracer: unavailable" - self.assertEqual(expected, tracer_line) + assert expected == tracer_line def f_one(*args, **kwargs): @@ -227,15 +224,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..b740d2df3 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -18,6 +18,7 @@ from coverage.misc import NoCode, NoSource from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin +import pytest TRY_EXECFILE = os.path.join(TESTS_DIR, "modules/process_test/try_execfile.py") @@ -30,27 +31,27 @@ 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'] == 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"]) @@ -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,42 @@ 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") + assert self.stderr() == "" + assert self.stdout() == "runmod1: passed hello\n" 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") + assert self.stderr() == "" + assert self.stdout() == "pkg1.__init__: pkg1\nrunmod2: passed hello\n" 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") + assert self.stderr() == "" + assert self.stdout() == "pkg1.__init__: pkg1\nrunmod3: passed hello\n" 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") + assert self.stderr() == "" + assert self.stdout() == "pkg1.__init__: pkg1\npkg1.__main__: passed hello\n" 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") + assert self.stderr() == "" + assert self.stdout() == "pkg1.__init__: pkg1\npkg1.sub.__main__: passed hello\n" 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") + assert self.stderr() == "" + assert self.stdout() == "pkg1.__init__: pkg1\npkg1.__init__: __main__\n" 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..1e0ddcfb3 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,11 @@ 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 +148,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, + assert matcher.match(canonical) == matches, \ "File %s should have matched as %s" % (filepath, matches) - ) def test_tree_matcher(self): matches_to_try = [ @@ -167,7 +164,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 +187,12 @@ def test_module_matcher(self): ] modules = ['test', 'py.test', 'mymain'] mm = ModuleMatcher(modules) - self.assertEqual( - mm.info(), + 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 +203,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 +237,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 +276,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): @@ -418,4 +411,4 @@ def setUp(self): 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..a25f76cb2 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -26,6 +26,7 @@ from tests.coveragetest import CoverageTest, TESTS_DIR from tests.goldtest import gold_path from tests.goldtest import compare, contains, doesnt_contain, contains_any +import pytest class HtmlTestHelpers(CoverageTest): @@ -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( - "<h1>«ταБЬℓσ»" - " numbers", index - ) + assert "<title>«ταБЬℓσ»" \ + " numbers" in index + assert "<h1>«ταБЬℓσ»" \ + " 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( - "<title>«ταБЬℓσ»" - " & stüff!", index - ) - self.assertIn( - "

«ταБЬℓσ»" - " & stüff!:", index - ) + assert "«ταБЬℓσ»" \ + " & stüff!" 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,15 @@ 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], + assert len(cov._warnings) == \ + 1, \ + "Expected a warning to be thrown when an invalid python file is parsed" + assert "Couldn't parse Python file" in \ + cov._warnings[0], \ "Warning message should be in 'invalid file' warning" - ) - self.assertIn( - "innocuous.py", - cov._warnings[0], + 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 +368,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 +416,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 +432,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 +450,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 +526,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 +576,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 +599,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 +609,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): diff --git a/tests/test_misc.py b/tests/test_misc.py index 2f6fbe7c1..85b3d8f8c 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,7 +72,7 @@ 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(".") diff --git a/tests/test_numbits.py b/tests/test_numbits.py index 232d48d3a..8216b628a 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): @@ -122,11 +122,9 @@ def test_numbits_union(self): ")" ) 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], + assert [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 - ) def test_numbits_intersection(self): res = self.cursor.execute( @@ -136,7 +134,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 +142,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..46c22f9c1 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,15 @@ 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, + assert cov._warnings == \ ["Trace function changed, measurement is likely wrong: None"] - ) else: - self.assertEqual(cov._warnings, []) + assert cov._warnings == [] class MemoryLeakTest(CoverageTest): @@ -224,9 +223,9 @@ 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 class PyexpatTest(CoverageTest): @@ -268,20 +267,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), + assert not any(f.endswith("pyexpat.c") for f in files), \ "Pyexpat.c is in the measured files!: %r:" % (files,) - ) class ExceptionTest(CoverageTest): @@ -393,7 +390,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 +448,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 @@ -489,15 +486,13 @@ def test_unsets_trace(): out = self.stdout().replace(self.last_module_name, "coverage_test") - self.assertEqual( - out, + assert 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" - ), - ) + ) @pytest.mark.expensive def test_atexit_gettrace(self): # pragma: no metacov @@ -523,13 +518,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,11 +552,11 @@ 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 == [] def test_unencodable_filename(self): # https://github.com/nedbat/coveragepy/issues/891 @@ -609,4 +604,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..49b23f9b6 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -11,6 +11,7 @@ from tests.coveragetest import CoverageTest, xfail from tests.helpers import arcz_to_arcs +import pytest class PythonParserTest(CoverageTest): @@ -40,9 +41,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 +54,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 +73,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 +87,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 +99,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 +108,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 +117,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 +133,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 +175,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 +189,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 +220,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 +253,19 @@ def func10(): thing(12) more_stuff(13) """) - self.assertEqual( - parser.missing_arc_description(1, 2), + assert 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), + assert parser.missing_arc_description(1, 3) == \ "line 1 didn't jump to line 3, because the condition on line 1 was never false" - ) - self.assertEqual( - parser.missing_arc_description(6, -5), - "line 6 didn't return from function 'func5', " + assert 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), + assert 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), + assert 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), + assert parser.missing_arc_description(11, 13) == \ "line 11 didn't jump to line 13, because the condition on line 11 was never false" - ) def test_missing_arc_descriptions_for_small_callables(self): parser = self.parse_text(u"""\ @@ -288,22 +277,14 @@ def test_missing_arc_descriptions_for_small_callables(self): ] x = 7 """) - self.assertEqual( - parser.missing_arc_description(2, -2), + assert parser.missing_arc_description(2, -2) == \ "line 2 didn't finish the lambda on line 2" - ) - self.assertEqual( - parser.missing_arc_description(3, -3), + assert 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), + assert 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), + assert parser.missing_arc_description(5, -5) == \ "line 5 didn't finish the set comprehension on line 5" - ) def test_missing_arc_descriptions_for_exceptions(self): parser = self.parse_text(u"""\ @@ -314,14 +295,10 @@ def test_missing_arc_descriptions_for_exceptions(self): except ValueError: print("yikes") """) - self.assertEqual( - parser.missing_arc_description(3, 4), + assert parser.missing_arc_description(3, 4) == \ "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), + assert parser.missing_arc_description(5, 6) == \ "line 5 didn't jump to line 6, because the exception caught by line 5 didn't happen" - ) def test_missing_arc_descriptions_for_finally(self): parser = self.parse_text(u"""\ @@ -346,56 +323,36 @@ def function(): that_thing(19) """) if env.PYBEHAVIOR.finally_jumps_back: - self.assertEqual( - parser.missing_arc_description(18, 5), + assert 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), + assert parser.missing_arc_description(5, 19) == \ "line 5 didn't jump to line 19, because the break on line 5 wasn't executed" - ) - self.assertEqual( - parser.missing_arc_description(18, 10), + assert parser.missing_arc_description(18, 10) == \ "line 18 didn't jump to line 10, because the continue on line 10 wasn't executed" - ) - self.assertEqual( - parser.missing_arc_description(10, 2), + assert parser.missing_arc_description(10, 2) == \ "line 10 didn't jump to line 2, because the continue on line 10 wasn't executed" - ) - self.assertEqual( - parser.missing_arc_description(18, 14), + assert parser.missing_arc_description(18, 14) == \ "line 18 didn't jump to line 14, 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', " + assert 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', " + assert parser.missing_arc_description(18, -1) == \ + "line 18 didn't except from function 'function', " \ "because the raise on line 16 wasn't executed" - ) else: - self.assertEqual( - parser.missing_arc_description(18, 19), + assert parser.missing_arc_description(18, 19) == \ "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 parser.missing_arc_description(18, 2) == \ + "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 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', " \ "because the return on line 14 wasn't executed" - ) def test_missing_arc_descriptions_bug460(self): parser = self.parse_text(u"""\ @@ -406,10 +363,8 @@ 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 +395,16 @@ class Bar: fname = fname + ".py" self.make_file(fname, text, newline=newline) parser = self.parse_file(fname) - self.assertEqual( - parser.exit_counts(), - counts, + 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 +419,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 +429,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..5da12d9c8 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -13,6 +13,7 @@ from coverage.python import get_python_source from tests.coveragetest import CoverageTest, TESTS_DIR +import pytest # A simple program and its token stream. @@ -67,23 +68,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,11 +129,9 @@ class SourceEncodingTest(CoverageTest): def test_detect_source_encoding(self): for _, source, expected in ENCODING_DECLARATION_SOURCES: - self.assertEqual( - source_encoding(source), - expected, + assert source_encoding(source) == \ + expected, \ "Wrong encoding in %r" % source - ) def test_detect_source_encoding_not_in_comment(self): if env.PYPY3: # pragma: no metacov @@ -141,35 +140,35 @@ def test_detect_source_encoding_not_in_comment(self): 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 +185,19 @@ 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, + assert source_encoding(neutered) == \ + DEF_ENCODING, \ "Wrong encoding in %r" % neutered - ) def test_two_encoding_declarations(self): input_src = textwrap.dedent(u"""\ @@ -214,7 +211,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 +225,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 +255,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 +273,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..3472522bf 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -18,6 +18,7 @@ from tests.coveragetest import CoverageTest from tests.helpers import CheckUniqueFilenames +import pytest class FakeConfig(object): @@ -53,10 +54,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 +75,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 +106,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 +131,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 +157,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 +170,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 +196,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 +225,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,9 +250,9 @@ 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 == "" class PluginWarningOnPyTracer(CoverageTest): @@ -304,11 +305,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 +371,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 +401,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() == 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 +427,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 +439,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 +454,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 +526,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 +568,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 +642,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 +651,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 +704,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 +943,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 +1049,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()), - ) + assert ['', 'doctest:HTML_TAG', 'test:HTML_TAG', 'test:RENDERERS'] == \ + 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 +1077,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 +1102,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 +1140,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 b44147a39..8362c1e15 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,17 +242,17 @@ 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") - 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. @@ -270,12 +270,12 @@ 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) @@ -283,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() @@ -294,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") @@ -307,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) @@ -321,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() @@ -333,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. @@ -355,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", """\ @@ -396,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) @@ -413,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", """\ @@ -445,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. @@ -457,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", """\ @@ -486,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", """\ @@ -512,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", """\ @@ -528,10 +528,10 @@ 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 def test_fork(self): if not hasattr(os, 'fork'): @@ -555,7 +555,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 @@ -566,7 +566,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") @@ -577,7 +577,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 @@ -598,7 +598,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 @@ -606,17 +606,15 @@ 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( - ("No module named no_such_module" in out) or + 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 def test_warnings_trace_function_changed_with_threads(self): # https://github.com/nedbat/coveragepy/issues/164 @@ -637,8 +635,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", """\ @@ -648,10 +646,10 @@ 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 def test_timid(self): # Test that the --timid command line argument properly swaps the tracer @@ -696,21 +694,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", """\ @@ -728,13 +726,13 @@ 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 @@ -758,13 +756,13 @@ 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)), @@ -788,7 +786,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. @@ -805,7 +803,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 @@ -827,18 +825,16 @@ 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 " + assert out == \ + "Run 1\n" \ + "Run 2\n" \ + "Coverage.py warning: Module foo was previously imported, but not measured " \ "(module-not-measured)\n" - ) def test_module_name(self): # 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") @@ -854,14 +850,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: @@ -958,8 +954,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 @@ -977,8 +973,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 @@ -996,7 +992,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 @@ -1037,10 +1033,10 @@ 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 def test_bug_862(self): if env.WINDOWS: @@ -1057,7 +1053,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 @@ -1108,17 +1104,17 @@ 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 def test_excepthook_exit(self): if not env.CPYTHON: @@ -1136,11 +1132,11 @@ 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 def test_excepthook_throw(self): if env.PYPY: @@ -1162,11 +1158,11 @@ 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 class AliasedCommandTest(CoverageTest): @@ -1183,20 +1179,20 @@ 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 = [ @@ -1206,8 +1202,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): @@ -1221,11 +1217,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) @@ -1250,29 +1246,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), + assert st == 2 + assert self.last_line_squeezed(out) == \ "Coverage failure: total of 43 is less than fail-under=44" - ) 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), + assert st == 2 + assert self.last_line_squeezed(out) == \ "Coverage failure: total of 42.86 is less than fail-under=42.88" - ) class FailUnderNoFilesTest(CoverageTest): @@ -1280,8 +1272,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): @@ -1290,9 +1282,9 @@ 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 class UnicodeFilePathsTest(CoverageTest): @@ -1307,23 +1299,23 @@ 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" @@ -1337,29 +1329,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='â']") @@ -1383,7 +1375,7 @@ 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 class YankedDirectoryTest(CoverageTest): @@ -1408,18 +1400,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(): @@ -1476,7 +1468,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: @@ -1524,13 +1516,13 @@ def test_subprocess_with_pth_files(self): # pragma: no metacov import main # pylint: disable=unused-import 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 # https://github.com/nedbat/coveragepy/issues/492 @@ -1546,7 +1538,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") @@ -1554,13 +1546,13 @@ 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) + assert len(data_files) == 1, \ + "Expected only .coverage after combine, looks like there are " \ + "extra data files that were not cleaned up: %r" % data_files class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest): @@ -1638,7 +1630,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") @@ -1646,8 +1638,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..f2c753179 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -19,6 +19,7 @@ from coverage.summary import SummaryReporter from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin +import pytest class SummaryTest(UsingModulesMixin, CoverageTest): @@ -44,7 +45,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 +56,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 +74,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 +93,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 +113,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 +132,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 +171,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 +183,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 +192,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 +214,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 +223,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 +239,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 +248,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 +271,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() @@ -282,7 +283,7 @@ def branch(x, y, z): '---------------------------------------------------------', '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 +299,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 +310,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 +340,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 +351,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 +380,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 +392,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 +406,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 +416,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 +427,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 +437,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 +463,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 +474,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 +493,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 +525,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 +536,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,10 +561,8 @@ 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 "Couldn't parse 'mycode.py' as Python source: 'error' at line 1" == \ + errmsg def test_accenteddotpy_not_python(self): if env.JYTHON: @@ -590,7 +589,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 +605,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 +624,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 +639,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 +653,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.""" @@ -683,7 +682,7 @@ def branch(x): import main # pragma: nested # pylint: disable=unused-import 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,16 +698,16 @@ 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 def test_pyw_files(self): if not env.WINDOWS: @@ -728,10 +727,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. @@ -748,7 +747,7 @@ def test_tracing_pyc_file(self): 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 def test_missing_py_file_during_run(self): # PyPy2 doesn't run bare .pyc files. @@ -768,7 +767,7 @@ 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. @@ -780,7 +779,7 @@ def test_missing_py_file_during_run(self): # 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 +803,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 +829,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 +895,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), + assert indexes == sorted(indexes), \ "The words %r don't appear in order in %r" % (words, text) - ) def test_sort_report_by_stmts(self): # Sort the text report by the Stmts column. @@ -946,5 +943,5 @@ def test_sort_report_by_cover(self): 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..2879f99de 100644 --- a/tests/test_templite.py +++ b/tests/test_templite.py @@ -9,6 +9,7 @@ from coverage.templite import Templite, TempliteSyntaxError, TempliteValueError from tests.coveragetest import CoverageTest +import pytest # pylint: disable=possibly-unused-variable @@ -39,7 +40,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 +49,13 @@ 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(), + 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..fd5a38da2 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -30,16 +30,14 @@ 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}) +"""Tests of helper methods on `backunittest.TestCase`.""" +deftest_assert_count_equal(self): + self.assertCountEqual(set(), set()) + self.assertCountEqual({1,2,3}, {3,1,2}) + with pytest.raises(AssertionError): + self.assertCountEqual({1,2,3}, set()) + withpytest.raises(AssertionError): + self.assertCountEqual({1,2,3}, {4,5,6}) class CoverageTestTest(CoverageTest): @@ -50,10 +48,10 @@ def test_file_exists(self): 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): + 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): + with pytest.raises(AssertionError, match=msg): self.assert_exists("shadow.txt") def test_file_count(self): @@ -68,24 +66,24 @@ def test_file_count(self): "3 != 13 : 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: " "['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: " "['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: []" ) - 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,7 +241,7 @@ 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") diff --git a/tests/test_version.py b/tests/test_version.py index 11b180d52..ce2e88e66 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -16,24 +16,20 @@ 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), + assert _make_url(4, 0, 0, 'final', 0) == \ "https://coverage.readthedocs.io" - ) - self.assertEqual( - _make_url(4, 1, 2, 'beta', 3), + assert _make_url(4, 1, 2, 'beta', 3) == \ "https://coverage.readthedocs.io/en/coverage-4.1.2b3" - ) diff --git a/tests/test_xml.py b/tests/test_xml.py index e3be7a543..033c374ee 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -17,6 +17,7 @@ from tests.coveragetest import CoverageTest from tests.goldtest import compare, gold_path +import pytest class XmlTestHelpers(CoverageTest): @@ -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,8 @@ 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) From 6e93714d655c87e62cd03a4ff3e1739245b684b9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 07:41:50 -0500 Subject: [PATCH 14/67] test: fix unittest2pytest brokenness unittest2pytest created syntax errors, reported here: https://github.com/pytest-dev/unittest2pytest/issues/51 This commit fixes them back. --- tests/test_backward.py | 21 ++++++++++++--------- tests/test_testing.py | 17 +++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/tests/test_backward.py b/tests/test_backward.py index 01cc8dac0..767a7ac83 100644 --- a/tests/test_backward.py +++ b/tests/test_backward.py @@ -5,14 +5,17 @@ from coverage.backunittest import TestCase from coverage.backward import iitems, binary_bytes, bytes_to_ints -"""Tests of things from backward.py.""" -deftest_iitems(self): - d = {'a': 1, 'b': 2, 'c': 3} - items = [('a', 1), ('b', 2), ('c', 3)] - self.assertCountEqual(list(iitems(d)), items) + +class BackwardTest(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) def test_binary_bytes(self): - byte_values = [0, 255, 17, 23, 42, 57] - bb = binary_bytes(byte_values) - assert len(bb) == len(byte_values) - assert byte_values == list(bytes_to_ints(bb)) + byte_values = [0, 255, 17, 23, 42, 57] + bb = binary_bytes(byte_values) + assert len(bb) == len(byte_values) + assert byte_values == list(bytes_to_ints(bb)) diff --git a/tests/test_testing.py b/tests/test_testing.py index fd5a38da2..52560a5f2 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -30,14 +30,15 @@ def test_xdist_sys_path_nuttiness_is_fixed(): assert os.environ.get('PYTHONPATH') is None -"""Tests of helper methods on `backunittest.TestCase`.""" -deftest_assert_count_equal(self): - self.assertCountEqual(set(), set()) - self.assertCountEqual({1,2,3}, {3,1,2}) - with pytest.raises(AssertionError): - self.assertCountEqual({1,2,3}, set()) - withpytest.raises(AssertionError): - self.assertCountEqual({1,2,3}, {4,5,6}) +class TestingTest(TestCase): + """Tests of helper methods on `backunittest.TestCase`.""" + + def test_assert_count_equal(self): + self.assertCountEqual(set(), set()) + with pytest.raises(AssertionError): + self.assertCountEqual({1,2,3}, set()) + with pytest.raises(AssertionError): + self.assertCountEqual({1,2,3}, {4,5,6}) class CoverageTestTest(CoverageTest): From 716b31214126c4a06d8262a62153e5d1fa45fa54 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 08:16:50 -0500 Subject: [PATCH 15/67] test: adapt to pytest assertion messages Bare "assert" statements don't produce the same assertion message as self.assertEqual did: they don't include the two values compared. For some of our own asserts, add back the detailed message. For some checks of asserts, it's fine that the values are missing because the longer messsage includes the information. --- tests/coveragetest.py | 8 ++++---- tests/test_testing.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index ed3f18397..2e6f1323c 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -198,7 +198,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. - assert 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 +210,7 @@ def check_coverage( missing_formatted = analysis.missing_formatted() if isinstance(missing, string_class): - assert missing_formatted == missing + assert missing_formatted == missing, "{!r} != {!r}".format(missing_formatted, missing) else: for missing_list in missing: if missing_formatted == missing_list: @@ -244,7 +244,7 @@ def check_coverage( frep = StringIO() cov.report(mod, file=frep, show_missing=True) rep = " ".join(frep.getvalue().split("\n")[2].split()[1:]) - assert report == rep + assert report == rep, "{!r} != {!r}".format(report, rep) return cov @@ -351,7 +351,7 @@ def command_line(self, args, ret=OK): """ ret_actual = command_line(args) - assert 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, diff --git a/tests/test_testing.py b/tests/test_testing.py index 52560a5f2..21e09dcc0 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -48,10 +48,10 @@ 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" + 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" + msg = "File 'shadow.txt' should exist" with pytest.raises(AssertionError, match=msg): self.assert_exists("shadow.txt") @@ -64,25 +64,25 @@ 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 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 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 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 pytest.raises(AssertionError, match=msg): self.assert_file_count("*.q", 10) From 4bad09744d80c6123e77252d5db09a31db25297b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 08:34:00 -0500 Subject: [PATCH 16/67] refactor: mark an internal method --- tests/coveragetest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 2e6f1323c..b0c99d2e5 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -134,7 +134,7 @@ 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): + def _assert_equal_arcs(self, a1, a2, msg=None): """Assert that the arc lists `a1` and `a2` are equal.""" # Make them into multi-line strings so we can see what's going wrong. s1 = arcs_to_arcz_repr(a1) @@ -225,17 +225,17 @@ def check_coverage( # print("Executed:") # print(" actual:", sorted(set(analysis.arcs_executed()))) with self.delayed_assertions(): - self.assert_equal_arcs( + self._assert_equal_arcs( arcs, analysis.arc_possibilities(), "Possible arcs differ: minus is expected, plus is actual" ) - self.assert_equal_arcs( + self._assert_equal_arcs( arcs_missing, analysis.arcs_missing(), "Missing arcs differ: minus is expected, plus is actual" ) - self.assert_equal_arcs( + self._assert_equal_arcs( arcs_unpredicted, analysis.arcs_unpredicted(), "Unpredicted arcs differ: minus is expected, plus is actual" ) From 65b5830a4d13d65c3bb314dd4894d565a16ceb9b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 09:12:43 -0500 Subject: [PATCH 17/67] style: singleton comparisons should use is I guess the original line was wrong, but it would have been nice for unittest2pytest to fix it for me: https://github.com/pytest-dev/unittest2pytest/issues/52 --- igor.py | 2 +- tests/test_api.py | 6 +++--- tests/test_arcs.py | 2 +- tests/test_config.py | 16 ++++++++-------- tests/test_context.py | 2 +- tests/test_data.py | 2 +- tests/test_execfile.py | 2 +- tests/test_plugins.py | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/igor.py b/igor.py index b2dc05cfe..d8d2069d8 100644 --- a/igor.py +++ b/igor.py @@ -157,7 +157,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. diff --git a/tests/test_api.py b/tests/test_api.py index 955599865..be7cc83e5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -116,7 +116,7 @@ def test_ignore_stdlib(self): # Measure without the stdlib. cov1 = coverage.Coverage() - assert 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 @@ -1108,9 +1108,9 @@ def test_config_doesnt_change(self): self.make_file("simple.py", "a = 1") cov = coverage.Coverage() self.start_import_stop(cov, "simple") - assert cov.get_option("report:show_missing") == False + assert cov.get_option("report:show_missing") is False cov.report(show_missing=True) - assert cov.get_option("report:show_missing") == False + assert cov.get_option("report:show_missing") is False class RelativePathTest(CoverageTest): diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 79280e2d6..c4d34d307 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -94,7 +94,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.") diff --git a/tests/test_config.py b/tests/test_config.py index 0a742f3dd..f1df4d724 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -232,7 +232,7 @@ def test_environment_vars_in_config(self): self.set_environ("OKAY", "yes") cov = coverage.Coverage() assert cov.config.data_file == "hello-world.fooey" - assert cov.config.branch == True + assert cov.config.branch is True assert cov.config.exclude_list == \ ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"] @@ -256,7 +256,7 @@ def test_environment_vars_in_toml_config(self): self.set_environ("THING", "ZZZ") cov = coverage.Coverage() assert cov.config.data_file == "hello-world.fooey" - assert cov.config.branch == True + assert cov.config.branch is True assert cov.config.exclude_list == \ ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"] @@ -582,8 +582,8 @@ def assert_config_settings_are_correct(self, cov): 'names': 'Jane/John/Jenny', } assert cov.config.get_plugin_options("plugins.another") == {} - assert cov.config.json_show_contexts == True - assert cov.config.json_pretty_print == True + 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="")) @@ -629,8 +629,8 @@ def check_other_not_read_if_coveragerc(self, fname): """) cov = coverage.Coverage() assert cov.config.run_include == ["foo"] - assert cov.config.run_omit == None - assert cov.config.branch == False + 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") @@ -646,8 +646,8 @@ def check_other_config_need_prefixes(self, fname): branch = true """) cov = coverage.Coverage() - assert cov.config.run_omit == None - assert 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") diff --git a/tests/test_context.py b/tests/test_context.py index be478760c..418849d51 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -285,4 +285,4 @@ def test_oldstyle(self): 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 - assert get_qualname() == None + assert get_qualname() is None diff --git a/tests/test_data.py b/tests/test_data.py index d1ed04e12..9eb6ecee9 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -416,7 +416,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, []) - assert 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): diff --git a/tests/test_execfile.py b/tests/test_execfile.py index b740d2df3..c758f7114 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -51,7 +51,7 @@ def test_run_python_file(self): assert mod_globs['argv1-n'] == ["arg1", "arg2"] # __builtins__ should have the right values, like open(). - assert 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. diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 3472522bf..e706ef36b 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -403,7 +403,7 @@ def test_plugin2_with_branch(self): analysis = cov._analyze("foo_7.html") assert analysis.statements == {1, 2, 3, 4, 5, 6, 7} # Plugins don't do branch coverage yet. - assert analysis.has_arcs() == True + assert analysis.has_arcs() is True assert analysis.arc_possibilities() == [] assert analysis.missing == {1, 2, 3, 6, 7} From 2faae252d853572cf7403528075461569ac137d1 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 09:18:40 -0500 Subject: [PATCH 18/67] style: correct placement of auto-added pytest imports --- tests/test_api.py | 2 +- tests/test_concurrency.py | 2 +- tests/test_config.py | 2 +- tests/test_coverage.py | 3 ++- tests/test_data.py | 2 +- tests/test_debug.py | 2 +- tests/test_execfile.py | 3 ++- tests/test_html.py | 2 +- tests/test_parser.py | 3 ++- tests/test_phystokens.py | 3 ++- tests/test_plugins.py | 3 ++- tests/test_summary.py | 3 ++- tests/test_templite.py | 3 ++- tests/test_xml.py | 2 +- 14 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index be7cc83e5..7d75022e6 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 @@ -22,7 +23,6 @@ from coverage.misc import CoverageException from tests.coveragetest import CoverageTest, CoverageTestMethodsMixin, TESTS_DIR, UsingModulesMixin -import pytest class ApiTest(CoverageTest): diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index cc9622d1d..fd7aa851d 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -6,6 +6,7 @@ import glob import os import random +import re import sys import threading import time @@ -20,7 +21,6 @@ from tests.coveragetest import CoverageTest from tests.helpers import remove_files -import re # These libraries aren't always available, we'll skip tests if they aren't. diff --git a/tests/test_config.py b/tests/test_config.py index f1df4d724..3d0906681 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,13 +7,13 @@ from collections import OrderedDict import mock +import pytest import coverage from coverage.misc import CoverageException from tests.coveragetest import CoverageTest, UsingModulesMixin from tests.helpers import without_module -import pytest class ConfigTest(CoverageTest): diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 6529aa726..52b405e84 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -4,12 +4,13 @@ """Tests for coverage.py.""" +import pytest + import coverage from coverage import env from coverage.misc import CoverageException from tests.coveragetest import CoverageTest -import pytest class TestCoverageTest(CoverageTest): diff --git a/tests/test_data.py b/tests/test_data.py index 9eb6ecee9..4eda18734 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,7 +20,6 @@ from coverage.misc import CoverageException from tests.coveragetest import CoverageTest -import pytest LINES_1 = { diff --git a/tests/test_debug.py b/tests/test_debug.py index 629665f96..42d7945eb 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -4,6 +4,7 @@ """Tests of coverage/debug.py""" import os +import re import pytest @@ -15,7 +16,6 @@ from tests.coveragetest import CoverageTest from tests.helpers import re_line, re_lines -import re class InfoFormatterTest(CoverageTest): diff --git a/tests/test_execfile.py b/tests/test_execfile.py index c758f7114..775274783 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 @@ -18,7 +20,6 @@ from coverage.misc import NoCode, NoSource from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin -import pytest TRY_EXECFILE = os.path.join(TESTS_DIR, "modules/process_test/try_execfile.py") diff --git a/tests/test_html.py b/tests/test_html.py index a25f76cb2..59cda0d4a 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 @@ -26,7 +27,6 @@ from tests.coveragetest import CoverageTest, TESTS_DIR from tests.goldtest import gold_path from tests.goldtest import compare, contains, doesnt_contain, contains_any -import pytest class HtmlTestHelpers(CoverageTest): diff --git a/tests/test_parser.py b/tests/test_parser.py index 49b23f9b6..14950c3d0 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -5,13 +5,14 @@ import textwrap +import pytest + from coverage import env from coverage.misc import NotPython from coverage.parser import PythonParser from tests.coveragetest import CoverageTest, xfail from tests.helpers import arcz_to_arcs -import pytest class PythonParserTest(CoverageTest): diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index 5da12d9c8..26a72d281 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -7,13 +7,14 @@ 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 from coverage.python import get_python_source from tests.coveragetest import CoverageTest, TESTS_DIR -import pytest # A simple program and its token stream. diff --git a/tests/test_plugins.py b/tests/test_plugins.py index e706ef36b..12ce7c1e8 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 @@ -18,7 +20,6 @@ from tests.coveragetest import CoverageTest from tests.helpers import CheckUniqueFilenames -import pytest class FakeConfig(object): diff --git a/tests/test_summary.py b/tests/test_summary.py index f2c753179..1d74af9cf 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 @@ -19,7 +21,6 @@ from coverage.summary import SummaryReporter from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin -import pytest class SummaryTest(UsingModulesMixin, CoverageTest): diff --git a/tests/test_templite.py b/tests/test_templite.py index 2879f99de..8d808554b 100644 --- a/tests/test_templite.py +++ b/tests/test_templite.py @@ -6,10 +6,11 @@ import re +import pytest + from coverage.templite import Templite, TempliteSyntaxError, TempliteValueError from tests.coveragetest import CoverageTest -import pytest # pylint: disable=possibly-unused-variable diff --git a/tests/test_xml.py b/tests/test_xml.py index 033c374ee..15f076983 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 @@ -17,7 +18,6 @@ from tests.coveragetest import CoverageTest from tests.goldtest import compare, gold_path -import pytest class XmlTestHelpers(CoverageTest): From a035bde3320a501720ae722dc6b54fe7ff5c8647 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 10:09:41 -0500 Subject: [PATCH 19/67] build: quiet a silly pylint warning It's good in tests to use `assert "expected" == actual()`, so why is pylint all up in my grill about it? --- pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/pylintrc b/pylintrc index cfe67f966..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, From d02be78c3a0ee3022be56c623bb9bdcad1e9acd4 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 10:10:47 -0500 Subject: [PATCH 20/67] style: fix long lines and avoid backslashes --- tests/coveragetest.py | 3 +- tests/test_api.py | 14 ++-- tests/test_arcs.py | 8 +-- tests/test_cmdline.py | 7 +- tests/test_config.py | 9 +-- tests/test_data.py | 16 +++-- tests/test_debug.py | 10 ++- tests/test_execfile.py | 3 +- tests/test_files.py | 14 ++-- tests/test_html.py | 30 ++++----- tests/test_numbits.py | 8 ++- tests/test_oddball.py | 22 +++--- tests/test_parser.py | 142 +++++++++++++++++++++++---------------- tests/test_phystokens.py | 8 +-- tests/test_plugins.py | 4 +- tests/test_process.py | 28 +++++--- tests/test_summary.py | 7 +- tests/test_templite.py | 3 +- tests/test_version.py | 7 +- tests/test_xml.py | 3 +- 20 files changed, 179 insertions(+), 167 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index b0c99d2e5..f7a5f6f81 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -210,7 +210,8 @@ def check_coverage( missing_formatted = analysis.missing_formatted() if isinstance(missing, string_class): - assert missing_formatted == missing, "{!r} != {!r}".format(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: diff --git a/tests/test_api.py b/tests/test_api.py index 7d75022e6..bce431f36 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -548,8 +548,7 @@ def test_warnings_suppressed(self): assert "Hello\n" in out err = self.stderr() - assert "Coverage.py warning: Module sys has no Python source. (module-not-python)" in \ - err + 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 @@ -634,8 +633,7 @@ def test_switch_context_testrunner(self): # Labeled data is collected data = cov.get_data() - assert [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'] @@ -673,8 +671,8 @@ def test_switch_context_with_static(self): # Labeled data is collected data = cov.get_data() - assert [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'] @@ -691,8 +689,8 @@ 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 - assert 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): diff --git a/tests/test_arcs.py b/tests/test_arcs.py index c4d34d307..2f49ecfbf 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -1410,10 +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() - assert fr.missing_arc_description(3, -3, arcs_executed) == \ - "line 3 didn't finish the generator expression on line 3" - assert 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): diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 2c24c5988..a07444524 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -544,10 +544,9 @@ def test_multiprocessing_needs_config_file(self): # 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) - assert "Options affecting multiprocessing must only be specified in a configuration file." in \ - self.stderr() - assert "Remove --branch from the command line." in \ - self.stderr() + msg = "Options affecting multiprocessing must only be specified in a configuration file." + assert msg in self.stderr() + assert "Remove --branch from the command line." in self.stderr() def test_run_debug(self): self.cmd_executes("run --debug=opt1 foo.py", """\ diff --git a/tests/test_config.py b/tests/test_config.py index 3d0906681..04503f309 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -88,8 +88,7 @@ def test_toml_config_file(self): 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"} + 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", """\ @@ -233,8 +232,7 @@ def test_environment_vars_in_config(self): cov = coverage.Coverage() 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"] + 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. @@ -257,8 +255,7 @@ def test_environment_vars_in_toml_config(self): cov = coverage.Coverage() 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"] + 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. diff --git a/tests/test_data.py b/tests/test_data.py index 4eda18734..789bdd5a2 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -209,8 +209,7 @@ def test_contexts_by_lineno_with_lines(self): covdata = CoverageData() covdata.set_context('test_a') covdata.add_lines(LINES_1) - assert 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() @@ -251,8 +250,8 @@ def test_contexts_by_lineno_with_arcs(self): covdata = CoverageData() covdata.set_context('test_x') covdata.add_arcs(ARCS_3) - assert 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() @@ -587,9 +586,12 @@ def test_debug_output_with_debug_option(self): covdata2.read() self.assert_line_counts(covdata2, SUMMARY_1) - assert re.search(r"^Erasing data file '.*\.coverage'\n" \ - r"Creating data file '.*\.coverage'\n" \ - r"Opening data file '.*\.coverage'\n$", debug.get_output()) + assert re.search( + r"^Erasing data file '.*\.coverage'\n" + r"Creating data file '.*\.coverage'\n" + r"Opening data file '.*\.coverage'\n$", + debug.get_output() + ) def test_debug_output_without_debug_option(self): # With a debug object, but not the dataio option, we don't get debug diff --git a/tests/test_debug.py b/tests/test_debug.py index 42d7945eb..16669407b 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -176,9 +176,8 @@ def test_debug_config(self): """.split() for label in labels: label_pat = r"^\s*%s: " % label - assert len(re_lines(out_lines, label_pat).splitlines()) == \ - 1, \ - "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"]) @@ -190,9 +189,8 @@ def test_debug_sys(self): """.split() for label in labels: label_pat = r"^\s*%s: " % label - assert len(re_lines(out_lines, label_pat).splitlines()) == \ - 1, \ - "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"]) diff --git a/tests/test_execfile.py b/tests/test_execfile.py index 775274783..db78d0f6a 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -39,8 +39,7 @@ def test_run_python_file(self): assert dunder_file == "try_execfile.py" # It should have its correct module data. - assert mod_globs['__doc__'].splitlines()[0] == \ - "Test file for run_python_file." + 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')" diff --git a/tests/test_files.py b/tests/test_files.py index 1e0ddcfb3..6a9556ecc 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -66,8 +66,7 @@ def test_canonical_filename_ensure_cache_hit(self): assert canonical_path == self.abs_path('file1.py') # After the filename has been converted, it should be in the cache. assert 'sub/proj1/file1.py' in files.CANONICAL_FILENAME_CACHE - assert files.canonical_filename('sub/proj1/file1.py') == \ - self.abs_path('file1.py') + assert files.canonical_filename('sub/proj1/file1.py') == self.abs_path('file1.py') @pytest.mark.parametrize("original, flat", [ @@ -148,8 +147,8 @@ def setUp(self): def assertMatches(self, matcher, filepath, matches): """The `matcher` should agree with `matches` about `filepath`.""" canonical = files.canonical_filename(filepath) - assert 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 = [ @@ -187,12 +186,9 @@ def test_module_matcher(self): ] modules = ['test', 'py.test', 'mymain'] mm = ModuleMatcher(modules) - assert mm.info() == \ - modules + assert mm.info() == modules for modulename, matches in matches_to_try: - assert mm.match(modulename) == \ - matches, \ - modulename + assert mm.match(modulename) == matches, modulename def test_fnmatch_matcher(self): matches_to_try = [ diff --git a/tests/test_html.py b/tests/test_html.py index 59cda0d4a..ee2eb5755 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -309,20 +309,20 @@ def test_non_ascii_title_set_in_config_file(self): self.make_file(".coveragerc", "[html]\ntitle = «ταБЬℓσ» numbers") self.run_coverage() index = self.get_html_index_content() - assert "«ταБЬℓσ»" \ - " numbers" in index - assert "<h1>«ταБЬℓσ»" \ - " numbers" in index + assert "<title>«ταБЬℓσ» numbers" in index + assert "<h1>«ταБЬℓσ» 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() - assert "<title>«ταБЬℓσ»" \ - " & stüff!" in index - assert "

«ταБЬℓσ»" \ - " & stüff!:" in index + expected = ( + "«ταБЬℓσ» " + + "& stüff!" + ) + assert expected in index + assert "

«ταБЬℓσ» & stüff!:" in index class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest): @@ -345,15 +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) - assert len(cov._warnings) == \ - 1, \ - "Expected a warning to be thrown when an invalid python file is parsed" - assert "Couldn't parse Python file" in \ - cov._warnings[0], \ - "Warning message should be in 'invalid file' warning" - assert "innocuous.py" in \ - 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") diff --git a/tests/test_numbits.py b/tests/test_numbits.py index 8216b628a..fc27a093e 100644 --- a/tests/test_numbits.py +++ b/tests/test_numbits.py @@ -121,10 +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]) - assert [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( diff --git a/tests/test_oddball.py b/tests/test_oddball.py index 46c22f9c1..afbf232ae 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -130,8 +130,7 @@ def recur(n): # Get a warning about the stackoverflow effect on the tracing function. if pytrace: # pragma: no metacov - assert cov._warnings == \ - ["Trace function changed, measurement is likely wrong: None"] + assert cov._warnings == ["Trace function changed, measurement is likely wrong: None"] else: assert cov._warnings == [] @@ -277,8 +276,8 @@ def foo(): # Make sure pyexpat isn't recorded as a source file. # https://github.com/nedbat/coveragepy/issues/419 files = cov.get_data().measured_files() - assert not 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): @@ -485,14 +484,13 @@ def test_unsets_trace(): ) out = self.stdout().replace(self.last_module_name, "coverage_test") - - assert 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 diff --git a/tests/test_parser.py b/tests/test_parser.py index 14950c3d0..6edb6d1a0 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -254,19 +254,24 @@ def func10(): thing(12) more_stuff(13) """) - assert parser.missing_arc_description(1, 2) == \ - "line 1 didn't jump to line 2, because the condition on line 1 was never true" - assert parser.missing_arc_description(1, 3) == \ - "line 1 didn't jump to line 3, because the condition on line 1 was never false" - assert parser.missing_arc_description(6, -5) == \ - "line 6 didn't return from function 'func5', " \ - "because the loop on line 6 didn't complete" - assert parser.missing_arc_description(6, 7) == \ - "line 6 didn't jump to line 7, because the loop on line 6 never started" - assert parser.missing_arc_description(11, 12) == \ - "line 11 didn't jump to line 12, because the condition on line 11 was never true" - assert parser.missing_arc_description(11, 13) == \ - "line 11 didn't jump to line 13, because the condition on line 11 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" + ) + 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"""\ @@ -278,14 +283,14 @@ def test_missing_arc_descriptions_for_small_callables(self): ] x = 7 """) - assert parser.missing_arc_description(2, -2) == \ - "line 2 didn't finish the lambda on line 2" - assert parser.missing_arc_description(3, -3) == \ - "line 3 didn't finish the generator expression on line 3" - assert parser.missing_arc_description(4, -4) == \ - "line 4 didn't finish the dictionary comprehension on line 4" - assert 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"""\ @@ -296,10 +301,16 @@ def test_missing_arc_descriptions_for_exceptions(self): except ValueError: print("yikes") """) - assert parser.missing_arc_description(3, 4) == \ - "line 3 didn't jump to line 4, because the exception caught by line 3 didn't happen" - assert parser.missing_arc_description(5, 6) == \ - "line 5 didn't jump to line 6, because the exception caught by line 5 didn't happen" + expected = ( + "line 3 didn't jump to line 4, " + + "because the exception caught by line 3 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"""\ @@ -324,36 +335,56 @@ def function(): that_thing(19) """) if env.PYBEHAVIOR.finally_jumps_back: - assert parser.missing_arc_description(18, 5) == \ - "line 18 didn't jump to line 5, because the break on line 5 wasn't executed" - assert parser.missing_arc_description(5, 19) == \ - "line 5 didn't jump to line 19, because the break on line 5 wasn't executed" - assert parser.missing_arc_description(18, 10) == \ - "line 18 didn't jump to line 10, because the continue on line 10 wasn't executed" - assert parser.missing_arc_description(10, 2) == \ - "line 10 didn't jump to line 2, because the continue on line 10 wasn't executed" - assert parser.missing_arc_description(18, 14) == \ - "line 18 didn't jump to line 14, because the return on line 14 wasn't executed" - assert parser.missing_arc_description(14, -1) == \ - "line 14 didn't return from function 'function', " \ - "because the return on line 14 wasn't executed" - assert parser.missing_arc_description(18, -1) == \ - "line 18 didn't except from function 'function', " \ - "because the raise on line 16 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" + ) + 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" + ) + 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" + ) + 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" + ) + 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: - assert parser.missing_arc_description(18, 19) == \ - "line 18 didn't jump to line 19, because the break on line 5 wasn't executed" - assert parser.missing_arc_description(18, 2) == \ - "line 18 didn't jump to line 2, " \ - "because the continue on line 10 wasn't executed" \ - " or " \ + expected = ( + "line 18 didn't jump to line 19, " + + "because the break on line 5 wasn't executed" + ) + 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" - assert 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"""\ @@ -364,8 +395,7 @@ def test_missing_arc_descriptions_bug460(self): } x = 6 """) - assert 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): @@ -396,9 +426,7 @@ class Bar: fname = fname + ".py" self.make_file(fname, text, newline=newline) parser = self.parse_file(fname) - assert 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", """\ diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index 26a72d281..c7375cb5d 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -130,9 +130,7 @@ class SourceEncodingTest(CoverageTest): def test_detect_source_encoding(self): for _, source, expected in ENCODING_DECLARATION_SOURCES: - assert source_encoding(source) == \ - expected, \ - "Wrong encoding in %r" % source + assert source_encoding(source) == expected, "Wrong encoding in %r" % source def test_detect_source_encoding_not_in_comment(self): if env.PYPY3: # pragma: no metacov @@ -196,9 +194,7 @@ def test_neuter_encoding_declaration(self): # The neutered source will be detected as having no encoding # declaration. - assert 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"""\ diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 12ce7c1e8..f53de4fb1 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -1050,8 +1050,8 @@ def test_plugin_standalone(self): # Labeled coverage is collected data = cov.get_data() filenames = self.get_measured_filenames(data) - assert ['', '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") assert [2] == data.lines(filenames['rendering.py']) data.set_query_context("test:HTML_TAG") diff --git a/tests/test_process.py b/tests/test_process.py index 8362c1e15..536199db1 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -611,8 +611,10 @@ def test_warns_if_never_run(self): assert "Exception" not in out out = self.run_command("coverage run -m no_such_module") - assert ("No module named no_such_module" in out) or \ + assert ( + ("No module named no_such_module" in out) or ("No module named 'no_such_module'" in out) + ) assert "warning" not in out assert "Exception" not in out @@ -825,11 +827,13 @@ def foo(): inst.save() """) out = self.run_command("python run_twice.py") - assert 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 @@ -1256,15 +1260,15 @@ def test_report_43_is_ok(self): def test_report_43_is_not_ok(self): st, out = self.run_command_status("coverage report --fail-under=44") assert st == 2 - assert self.last_line_squeezed(out) == \ - "Coverage failure: total of 43 is less than fail-under=44" + 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") assert st == 2 - assert self.last_line_squeezed(out) == \ - "Coverage failure: total of 42.86 is less than fail-under=42.88" + expected = "Coverage failure: total of 42.86 is less than fail-under=42.88" + assert expected == self.last_line_squeezed(out) class FailUnderNoFilesTest(CoverageTest): @@ -1550,9 +1554,11 @@ def test_subprocess_with_pth_files_and_parallel(self): # pragma: no metacov # assert that there are *no* extra data files left over after a combine data_files = glob.glob(os.getcwd() + '/.coverage*') - assert len(data_files) == 1, \ - "Expected only .coverage after combine, looks like there are " \ + 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): diff --git a/tests/test_summary.py b/tests/test_summary.py index 1d74af9cf..e3694000f 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -562,8 +562,7 @@ 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) - assert "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" def test_accenteddotpy_not_python(self): if env.JYTHON: @@ -923,8 +922,8 @@ 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 - assert 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. diff --git a/tests/test_templite.py b/tests/test_templite.py index 8d808554b..770e97f97 100644 --- a/tests/test_templite.py +++ b/tests/test_templite.py @@ -55,8 +55,7 @@ def assertSynErr(self, msg): def test_passthrough(self): # Strings without variables are passed through unchanged. assert Templite("Hello").render() == "Hello" - assert Templite("Hello, 20% fun time!").render() == \ - "Hello, 20% fun time!" + assert Templite("Hello, 20% fun time!").render() == "Hello, 20% fun time!" def test_variables(self): # Variables use {{var}} syntax. diff --git a/tests/test_version.py b/tests/test_version.py index ce2e88e66..00d65624f 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -29,7 +29,6 @@ def test_make_version(self): assert _make_version(5, 10, 2, 'candidate', 7) == "5.10.2rc7" def test_make_url(self): - assert _make_url(4, 0, 0, 'final', 0) == \ - "https://coverage.readthedocs.io" - assert _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 15f076983..13e015d6b 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -275,8 +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`.""" - assert 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) From f18dd6884665f915868d199917075682f7e2e151 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 11:13:54 -0500 Subject: [PATCH 21/67] test: configure pytest assertion rewriting in coveragetest.py --- tests/conftest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 10761cddf..c2e0a893a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,10 @@ 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 can take additional options: # $set_env.py: PYTEST_ADDOPTS - Extra arguments to pytest. From bf29fdcf295b32e88a7407c244a5c703f0499ca9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 31 Jan 2021 11:31:43 -0500 Subject: [PATCH 22/67] test: keep multi-assert arc diffs working We don't have a way to do multi-assert in the pytest we're running, so cobble it together ourselves. --- tests/coveragetest.py | 46 +++++++++++++++++++++---------------------- tests/test_arcs.py | 4 ---- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index f7a5f6f81..c4a46da93 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -5,6 +5,7 @@ import contextlib import datetime +import difflib import functools import glob import os @@ -16,10 +17,7 @@ import types import pytest -from unittest_mixins import ( - EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin, - DelayedAssertionMixin, -) +from unittest_mixins import EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin import coverage from coverage import env @@ -67,7 +65,6 @@ class CoverageTest( EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin, - DelayedAssertionMixin, CoverageTestMethodsMixin, TestCase, ): @@ -134,12 +131,22 @@ 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) - assert s1 == s2, msg + if s1 != s2: + lines1 = s1.splitlines(keepends=True) + lines2 = s2.splitlines(keepends=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="", @@ -225,21 +232,14 @@ 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() diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 2f49ecfbf..3b63bcc2f 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -270,13 +270,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 +284,6 @@ def test_while_1(self): assert a == 4 and i == 3 """, arcz=arcz, - arcz_missing=arcz_missing, ) def test_while_true(self): From bc9ee777a80ff4853b062d9363b459aa47ccf1f6 Mon Sep 17 00:00:00 2001 From: Arthur Deygin <29574203+artdgn@users.noreply.github.com> Date: Mon, 1 Feb 2021 14:22:02 +1100 Subject: [PATCH 23/67] update config file doc to mention descending sort --- doc/config.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/config.rst b/doc/config.rst index e5c70b5a7..5514d6f85 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 (e.g. "-cover"). .. _config_html: From 35d91c7aa186a84d0edb333bad60b520f3b1d719 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 1 Feb 2021 08:25:06 -0500 Subject: [PATCH 24/67] doc: avoid latin abbreviations --- CONTRIBUTORS.txt | 1 + doc/config.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b43eeaf82..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 diff --git a/doc/config.rst b/doc/config.rst index 5514d6f85..34da8a066 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -319,7 +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 (e.g. "-cover"). +Prefix with ``-`` for descending sort (for example, "-cover"). .. _config_html: From 53354d78837de6b94833052ff1ca3d2797e7549e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 1 Feb 2021 22:33:50 -0500 Subject: [PATCH 25/67] refactor: remove unused methods --- coverage/backunittest.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/coverage/backunittest.py b/coverage/backunittest.py index 123bb2a13..8b66c0f13 100644 --- a/coverage/backunittest.py +++ b/coverage/backunittest.py @@ -18,16 +18,8 @@ class TestCase(unittest.TestCase): `unittest` doesn't have them. """ - # pylint: disable=signature-differs, deprecated-method + # pylint: disable=signature-differs 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) From 9012623110f49635fff61d19a4f5bb779de91b99 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 2 Feb 2021 09:04:58 -0500 Subject: [PATCH 26/67] refactor: move test mixins to their own file --- tests/coveragetest.py | 32 +++----------------------------- tests/mixins.py | 39 +++++++++++++++++++++++++++++++++++++++ tests/test_api.py | 4 ++-- tests/test_testing.py | 3 ++- 4 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 tests/mixins.py diff --git a/tests/coveragetest.py b/tests/coveragetest.py index c4a46da93..eeabfb46d 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -6,7 +6,6 @@ import contextlib import datetime import difflib -import functools import glob import os import os.path @@ -14,20 +13,19 @@ import re import shlex import sys -import types import pytest from unittest_mixins import EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin import coverage from coverage import env -from coverage.backunittest import TestCase, unittest +from coverage.backunittest import TestCase 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 run_command, SuperModuleCleaner +from tests.mixins import StopEverythingMixin # Status returns for the command line. @@ -37,35 +35,11 @@ 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, - CoverageTestMethodsMixin, + StopEverythingMixin, TestCase, ): """A base class for coverage.py test cases.""" diff --git a/tests/mixins.py b/tests/mixins.py new file mode 100644 index 000000000..97ca093c5 --- /dev/null +++ b/tests/mixins.py @@ -0,0 +1,39 @@ +# 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 + +from coverage.backunittest import unittest +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', (), {}) diff --git a/tests/test_api.py b/tests/test_api.py index bce431f36..6c3227952 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,7 +22,7 @@ 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 class ApiTest(CoverageTest): @@ -794,7 +794,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 diff --git a/tests/test_testing.py b/tests/test_testing.py index 21e09dcc0..b6ffe1a07 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -17,11 +17,12 @@ 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, CheckUniqueFilenames, re_lines, re_line, without_module, ) +from tests.mixins import convert_skip_exceptions def test_xdist_sys_path_nuttiness_is_fixed(): From 80c021d9174e7ae3e5183f1902903fb90a891246 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 2 Feb 2021 09:12:47 -0500 Subject: [PATCH 27/67] refactor: remove reliance on unittest_mixins.StdStreamCapturingMixin This is another step toward removing unittest.TestCase as a base class. --- tests/coveragetest.py | 4 ++-- tests/mixins.py | 31 +++++++++++++++++++++++++++++++ tests/test_api.py | 8 ++------ tests/test_arcs.py | 5 +++-- tests/test_cmdline.py | 10 ++++++---- tests/test_execfile.py | 30 ++++++++++++++++++------------ 6 files changed, 62 insertions(+), 26 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index eeabfb46d..71f4e2c86 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -15,7 +15,7 @@ import sys import pytest -from unittest_mixins import EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin +from unittest_mixins import EnvironmentAwareMixin, TempDirMixin import coverage from coverage import env @@ -25,7 +25,7 @@ from tests.helpers import arcs_to_arcz_repr, arcz_to_arcs from tests.helpers import run_command, SuperModuleCleaner -from tests.mixins import StopEverythingMixin +from tests.mixins import StdStreamCapturingMixin, StopEverythingMixin # Status returns for the command line. diff --git a/tests/mixins.py b/tests/mixins.py index 97ca093c5..ab0623c0c 100644 --- a/tests/mixins.py +++ b/tests/mixins.py @@ -10,6 +10,8 @@ import functools import types +import pytest + from coverage.backunittest import unittest from coverage.misc import StopEverything @@ -37,3 +39,32 @@ def __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 6c3227952..ea625ff18 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -520,10 +520,8 @@ def test_warnings(self): self.start_import_stop(cov, "hello") cov.get_data() - out = self.stdout() + out, err = self.stdouterr() assert "Hello\n" in out - - err = self.stderr() 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) @@ -544,10 +542,8 @@ def test_warnings_suppressed(self): self.start_import_stop(cov, "hello") cov.get_data() - out = self.stdout() + out, err = self.stdouterr() assert "Hello\n" in out - - err = self.stderr() 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 diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 3b63bcc2f..66777751a 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -1370,7 +1370,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("""\ @@ -1383,7 +1384,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)]) - assert 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 diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index a07444524..0eb26cd00 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -545,8 +545,9 @@ def test_multiprocessing_needs_config_file(self): # config file. self.command_line("run --concurrency=multiprocessing --branch foo.py", ret=ERR) msg = "Options affecting multiprocessing must only be specified in a configuration file." - assert msg in self.stderr() - assert "Remove --branch from the command line." in self.stderr() + _, 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", """\ @@ -915,8 +916,9 @@ def test_normal(self): def test_raise(self): ret = coverage.cmdline.main(['raise']) assert ret == 1 - assert self.stdout() == "" - err = self.stderr().split('\n') + 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!' diff --git a/tests/test_execfile.py b/tests/test_execfile.py index db78d0f6a..d221f2d38 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -193,33 +193,39 @@ class RunModuleTest(UsingModulesMixin, CoverageTest): def test_runmod1(self): run_python_module(["runmod1", "hello"]) - assert self.stderr() == "" - assert 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"]) - assert self.stderr() == "" - assert 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"]) - assert self.stderr() == "" - assert 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"]) - assert self.stderr() == "" - assert 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"]) - assert self.stderr() == "" - assert 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?"]) - assert self.stderr() == "" - assert 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 pytest.raises(NoSource, match="No module named '?i_dont_exist'?"): From 2e4b2977c78e254797d07c39e933fd535d4b0cec Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 07:21:13 -0500 Subject: [PATCH 28/67] refactor: remove unittest.assertCountEqual Another step toward removing unittest.TestCase. --- coverage/backunittest.py | 25 ------------------------- tests/conftest.py | 1 + tests/coveragetest.py | 8 ++++---- tests/helpers.py | 11 +++++++++++ tests/mixins.py | 2 +- tests/test_api.py | 3 ++- tests/test_arcs.py | 3 ++- tests/test_backward.py | 9 ++++++--- tests/test_context.py | 23 +++++++++++++---------- tests/test_data.py | 13 +++++++------ tests/test_testing.py | 20 +++++++++----------- 11 files changed, 56 insertions(+), 62 deletions(-) delete mode 100644 coverage/backunittest.py diff --git a/coverage/backunittest.py b/coverage/backunittest.py deleted file mode 100644 index 8b66c0f13..000000000 --- a/coverage/backunittest.py +++ /dev/null @@ -1,25 +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 - - if not unittest_has('assertCountEqual'): - def assertCountEqual(self, *args, **kwargs): - return self.assertItemsEqual(*args, **kwargs) diff --git a/tests/conftest.py b/tests/conftest.py index c2e0a893a..0ce494c8b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,7 @@ # 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. diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 71f4e2c86..69dbb7cfe 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -13,17 +13,17 @@ import re import shlex import sys +import unittest import pytest from unittest_mixins import EnvironmentAwareMixin, TempDirMixin import coverage from coverage import env -from coverage.backunittest import TestCase from coverage.backward import StringIO, import_local_file, string_class, shlex_quote from coverage.cmdline import CoverageScript -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 @@ -40,7 +40,7 @@ class CoverageTest( StdStreamCapturingMixin, TempDirMixin, StopEverythingMixin, - TestCase, + unittest.TestCase, ): """A base class for coverage.py test cases.""" @@ -283,7 +283,7 @@ 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.""" diff --git a/tests/helpers.py b/tests/helpers.py index 0621d7a94..1348aad69 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 @@ -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 index ab0623c0c..9d096d4d5 100644 --- a/tests/mixins.py +++ b/tests/mixins.py @@ -9,10 +9,10 @@ import functools import types +import unittest import pytest -from coverage.backunittest import unittest from coverage.misc import StopEverything diff --git a/tests/test_api.py b/tests/test_api.py index ea625ff18..0c1c9035f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -23,6 +23,7 @@ from coverage.misc import CoverageException from tests.coveragetest import CoverageTest, StopEverythingMixin, TESTS_DIR, UsingModulesMixin +from tests.helpers import assert_count_equal class ApiTest(CoverageTest): @@ -43,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() diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 66777751a..c6cf7952b 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 @@ -1732,4 +1733,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 767a7ac83..d750022b3 100644 --- a/tests/test_backward.py +++ b/tests/test_backward.py @@ -3,16 +3,19 @@ """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 -class BackwardTest(TestCase): +from tests.helpers import assert_count_equal + +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] diff --git a/tests/test_context.py b/tests/test_context.py index 418849d51..20b7a290f 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -12,6 +12,7 @@ from coverage.data import CoverageData from tests.coveragetest import CoverageTest +from tests.helpers import assert_count_equal class StaticContextTest(CoverageTest): @@ -22,14 +23,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 @@ -67,7 +68,7 @@ def test_combining_line_contexts(self): 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'] @@ -92,7 +93,7 @@ def test_combining_arc_contexts(self): 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'] @@ -157,13 +158,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 +180,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) diff --git a/tests/test_data.py b/tests/test_data.py index 789bdd5a2..fe37bd9e7 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -20,6 +20,7 @@ from coverage.misc import CoverageException from tests.coveragetest import CoverageTest +from tests.helpers import assert_count_equal LINES_1 = { @@ -82,23 +83,23 @@ def assert_line_counts(self, covdata, counts, fullpath=False): 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) + 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) + 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() diff --git a/tests/test_testing.py b/tests/test_testing.py index b6ffe1a07..67ec8dff4 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -8,18 +8,18 @@ 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 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 @@ -31,15 +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()) - with pytest.raises(AssertionError): - self.assertCountEqual({1,2,3}, set()) - with pytest.raises(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): From a9b259732e6cc96afd29c902670a0d4c9177714e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 07:54:16 -0500 Subject: [PATCH 29/67] build: improved combined coverage action - codecov wants more history. - make a downloadable HTML report. --- .github/workflows/coverage.yml | 36 ++++++++++++++++++++++++++------- .github/workflows/testsuite.yml | 5 ++++- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ad5a21cf8..9629463ec 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,7 +6,9 @@ name: "Coverage" on: push: branches: ["master"] - pull_request: + # as currently structured, this adds too many jobs (checks?), so don't run it + # on pull requests yet. + #pull_request: workflow_dispatch: defaults: @@ -15,16 +17,24 @@ 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: - "2.7" - "3.5" - "3.9" - "pypy3" + exclude: + # Windows PyPy doesn't seem to work? + - os: windows-latest + python-version: "pypy3" fail-fast: false steps: @@ -36,6 +46,11 @@ jobs: 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 @@ -51,13 +66,13 @@ jobs: set -xe python -m tox python -m igor combine_html - mv .metacov .coverage.${{ matrix.python-version }} + 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 +82,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" @@ -90,10 +107,15 @@ jobs: - name: "Combine and report" run: | set -xe - coverage combine - coverage xml + python -m igor combine_html - 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 diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index 59f5380b2..25fcd73d8 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -20,7 +20,10 @@ jobs: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: + - ubuntu-latest + - macos-latest + - windows-latest python-version: - "2.7" - "3.5" From 5dbf7010ad9f88019b1b3a7e47aa05278ee00e30 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 13:10:21 -0500 Subject: [PATCH 30/67] test: more-uniform skipping of test during metacov --- metacov.ini | 1 + tests/test_oddball.py | 5 ++--- tests/test_process.py | 35 +++++++++++++---------------------- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/metacov.ini b/metacov.ini index daabbf82f..1ec94fda7 100644 --- a/metacov.ini +++ b/metacov.ini @@ -49,6 +49,7 @@ exclude_lines = # Lines that we can't run during metacov. pragma: no metacov + pytest.mark.skipif\(env.METACOV # These lines only happen if tests fail. raise AssertionError diff --git a/tests/test_oddball.py b/tests/test_oddball.py index afbf232ae..f45656071 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -493,11 +493,10 @@ def test_unsets_trace(): 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 diff --git a/tests/test_process.py b/tests/test_process.py index 536199db1..14e7213cf 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -618,10 +618,9 @@ def test_warns_if_never_run(self): 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 @@ -653,6 +652,9 @@ def test_warning_trace_function_changed(self): 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. @@ -663,11 +665,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. @@ -737,11 +734,12 @@ def f(): assert msg in out @pytest.mark.expensive - def test_fullcoverage(self): # pragma: no metacov + @pytest.mark.skipif(env.METACOV, reason="Can't test fullcoverage when measuring ourselves") + def test_fullcoverage(self): 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: + # It only works with the C tracer. + if not env.C_TRACER: self.skipTest("fullcoverage only works with the C tracer.") # fullcoverage is a trick to get stdlib modules measured from @@ -1482,6 +1480,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.""" @@ -1501,10 +1500,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. @@ -1528,11 +1524,8 @@ def test_subprocess_with_pth_files(self): # pragma: no metacov data.read() 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 @@ -1575,9 +1568,10 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest): """ + @pytest.mark.skipif(env.METACOV, reason="Can't test sub-process pth file during metacoverage") 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: @@ -1592,9 +1586,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: From d5e834c27092014f6b91d35b8232bab96c1ec6a1 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 17:29:19 -0500 Subject: [PATCH 31/67] test: these tests can run during metacov I forget why I thought they couldn't run during meta-coverage. --- tests/test_process.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_process.py b/tests/test_process.py index 14e7213cf..04a6ecdeb 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1568,7 +1568,6 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest): """ - @pytest.mark.skipif(env.METACOV, reason="Can't test sub-process pth file during metacoverage") def assert_pth_and_source_work_together( self, dashm, package, source ): From 5eb2f15608bead6961da6ba9f56ebbfc6308254e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 17:32:18 -0500 Subject: [PATCH 32/67] test: quiet a misleading coverage miss We need this on Python 2, but in testing environments, a backport is installed, so this import isn't used. --- coverage/backward.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coverage/backward.py b/coverage/backward.py index 8af3452b2..d93ed4789 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -78,7 +78,9 @@ try: import reprlib -except ImportError: +except ImportError: # pragma: part 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 From 0fb366beeb41cb3388534105d2a76b2643e47022 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 17:42:09 -0500 Subject: [PATCH 33/67] test: metacov is always xdist --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0ce494c8b..81ec9f775 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -82,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. From 6f10e253affa99fd64d3ec6870d4c4264afea95c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 19:29:55 -0500 Subject: [PATCH 34/67] refactor: make all coverage.env uses uniform --- tests/test_debug.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_debug.py b/tests/test_debug.py index 16669407b..55001c96a 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -9,10 +9,10 @@ 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 @@ -195,7 +195,7 @@ def test_debug_sys(self): 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" From e032e5827e0d1fb2e8e750cfa1ce730ffd358b69 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 19:49:34 -0500 Subject: [PATCH 35/67] refactor: we weren't using this __eq__ --- coverage/backward.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/coverage/backward.py b/coverage/backward.py index d93ed4789..3b21fc99f 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -217,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. From 465dace54a3f3300c0a86b527a8f77d0475fc895 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 19:50:01 -0500 Subject: [PATCH 36/67] refactor: a better way to skip these tests --- tests/test_misc.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_misc.py b/tests/test_misc.py index 85b3d8f8c..dad542acf 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -76,16 +76,12 @@ def test_actual_errors(self): 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): From c0921466d3d235f10be333da1f9cf523f4e2e24c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Feb 2021 21:55:29 -0500 Subject: [PATCH 37/67] refactor: convert all skipping to pytest skips --- tests/coveragetest.py | 2 +- tests/test_api.py | 8 +++--- tests/test_arcs.py | 16 +++++------ tests/test_cmdline.py | 6 ++--- tests/test_concurrency.py | 7 ++--- tests/test_context.py | 5 ++-- tests/test_coverage.py | 7 ++--- tests/test_execfile.py | 2 +- tests/test_files.py | 6 +---- tests/test_oddball.py | 16 ++++------- tests/test_phystokens.py | 6 ++--- tests/test_plugins.py | 10 ++----- tests/test_process.py | 56 +++++++++++++-------------------------- tests/test_summary.py | 13 +++------ 14 files changed, 53 insertions(+), 107 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 69dbb7cfe..8e5f2b0a5 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -389,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. diff --git a/tests/test_api.py b/tests/test_api.py index 0c1c9035f..66f471c3c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -763,14 +763,12 @@ def test_current(self): assert cur0 is cur3 +@pytest.mark.skip(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") diff --git a/tests/test_arcs.py b/tests/test_arcs.py index c6cf7952b..83e9e6b11 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -1085,9 +1085,10 @@ def double_inputs(): ) 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 @@ -1332,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, @@ -1581,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 diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 0eb26cd00..d51410280 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -816,10 +816,9 @@ def test_version(self): 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"] @@ -828,6 +827,7 @@ def test_help_contains_command_name(self): out = self.stdout() 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. # @@ -835,8 +835,6 @@ 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"] diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index fd7aa851d..86c69cf50 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -12,6 +12,7 @@ import time from flaky import flaky +import pytest import coverage from coverage import env @@ -363,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="" ): diff --git a/tests/test_context.py b/tests/test_context.py index 20b7a290f..f51befae3 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -6,6 +6,8 @@ import inspect import os.path +import pytest + import coverage from coverage import env from coverage.context import qualname_from_frame @@ -279,9 +281,8 @@ def test_changeling(self): c.meth = 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") assert OldStyle().meth() == "tests.test_context.OldStyle.meth" assert OldChild().meth() == "tests.test_context.OldStyle.meth" diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 52b405e84..00648c189 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -343,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" % @@ -486,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): diff --git a/tests/test_execfile.py b/tests/test_execfile.py index d221f2d38..3cdd1ed9b 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -110,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(): diff --git a/tests/test_files.py b/tests/test_files.py index 6a9556ecc..6040b8898 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -396,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): assert actual_path(r'c:\Windows') == actual_path(r'C:\wINDOWS') diff --git a/tests/test_oddball.py b/tests/test_oddball.py index f45656071..b73078877 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -145,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.. @@ -194,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", """\ @@ -227,13 +224,11 @@ def f(): 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 @@ -555,10 +550,9 @@ def test_correct_filename(self): 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") diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index c7375cb5d..86b1fdbe4 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -132,11 +132,9 @@ def test_detect_source_encoding(self): for _, source, expected in ENCODING_DECLARATION_SOURCES: 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' assert source_encoding(source) == DEF_ENCODING diff --git a/tests/test_plugins.py b/tests/test_plugins.py index f53de4fb1..aeffdb808 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -256,12 +256,10 @@ def coverage_init(reg, options): 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() @@ -274,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.""" diff --git a/tests/test_process.py b/tests/test_process.py index 04a6ecdeb..01fd1eb81 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -533,10 +533,8 @@ def f1(): 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 @@ -735,13 +733,9 @@ def f(): @pytest.mark.expensive @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): - 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. - if not env.C_TRACER: - self.skipTest("fullcoverage only works with the C tracer.") - # 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. @@ -768,10 +762,9 @@ def test_fullcoverage(self): 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 @@ -881,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()) @@ -911,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", "") @@ -1040,9 +1035,8 @@ def test_coverage_custom_script(self): out = self.run_command("python -m run_coverage run how_is_it.py") 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", """\ @@ -1118,9 +1112,10 @@ def excepthook(*args): data.read() 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 @@ -1140,9 +1135,8 @@ def excepthook(*args): 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 @@ -1167,16 +1161,12 @@ def excepthook(*args): 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] @@ -1289,14 +1279,10 @@ def test_report(self): 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')") @@ -1380,14 +1366,10 @@ def test_accented_directory(self): 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 diff --git a/tests/test_summary.py b/tests/test_summary.py index e3694000f..0632c8f50 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -564,10 +564,8 @@ def test_dotpy_not_python(self): errmsg = re.sub(r": '.*' at", ": 'error' at", 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. @@ -709,10 +707,8 @@ def test_bug_203_mixed_case_listed_twice(self): 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 @@ -749,11 +745,8 @@ def test_tracing_pyc_file(self): report = self.get_report(cov).splitlines() 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") From 9d0ecedf25704f2e2a7e54271d66f7aab292b97e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 21 Feb 2021 14:26:28 -0500 Subject: [PATCH 38/67] fix: avoid tracing pytracer.py Also, adjust the logging available in pytracer --- coverage/pytracer.py | 45 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) 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: From 8286807af35162ee9bbae33c3165a93d34e455ac Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 21 Feb 2021 14:44:01 -0500 Subject: [PATCH 39/67] build: pin astroid since pylint doesn't Without this pin, pylint runs can change over time as astroid changes. --- requirements/dev.pip | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/dev.pip b/requirements/dev.pip index 321dd156d..608df1cfa 100644 --- a/requirements/dev.pip +++ b/requirements/dev.pip @@ -14,6 +14,7 @@ pluggy==0.13.1 # for linting. greenlet==0.4.16 +astroid==2.4.2 pylint==2.6.0 check-manifest==0.46 readme_renderer==26.0 From 5746b37ea1415b78a9216a68504fc46cc6237434 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 21 Feb 2021 15:22:28 -0500 Subject: [PATCH 40/67] build: publish metacov to a ghpages repo --- .github/workflows/coverage.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9629463ec..59e7ccc68 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -119,3 +119,22 @@ jobs: with: name: html_report path: htmlcov + + - name: Pushes to another 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/${{ github.sha }}' + empty-repository: false + create-destination-directory: true + target-branch: main + commit-message: "Coverage report for ${{ github.sha }}" + user-email: ned@nedbatchelder.com + + - name: Show link to report + run: | + echo "https://nedbat.github.io/coverage-reports/reports/${{ github.sha }}/htmlcov" From feb0bb76c6bc3dddcbc3a1c16c189a538729ac0c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 21 Feb 2021 16:36:29 -0500 Subject: [PATCH 41/67] build: use a nicer directory name for reports --- .github/workflows/coverage.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 59e7ccc68..5727559a8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -120,6 +120,11 @@ jobs: name: html_report path: htmlcov + - name: Get information for URL + id: info + run: | + echo "::set-output name=slug::$(date +'%Y%m%d')_$(echo ${{github.sha}} | cut -c 1-12)" + - name: Pushes to another repository uses: sebastian-palma/github-action-push-to-another-repository@allow-creating-destination-directory env: @@ -128,7 +133,7 @@ jobs: source-directory: 'htmlcov' destination-github-username: 'nedbat' destination-repository-name: 'coverage-reports' - destination-repository-directory: 'reports/${{ github.sha }}' + destination-repository-directory: 'reports/${{ steps.info.outputs.slug }}' empty-repository: false create-destination-directory: true target-branch: main @@ -137,4 +142,4 @@ jobs: - name: Show link to report run: | - echo "https://nedbat.github.io/coverage-reports/reports/${{ github.sha }}/htmlcov" + echo "https://nedbat.github.io/coverage-reports/reports/${{ steps.info.outputs.slug }}/htmlcov" From 72baa75af0db9455ae981731897957872c8cc6bb Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 21 Feb 2021 20:05:04 -0500 Subject: [PATCH 42/67] build: create an HTML file to redirect to the coverage report --- .github/workflows/coverage.yml | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5727559a8..60ae9e037 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -120,11 +120,16 @@ jobs: name: html_report path: htmlcov - - name: Get information for URL - id: info + - name: Create slug + id: slug run: | echo "::set-output name=slug::$(date +'%Y%m%d')_$(echo ${{github.sha}} | cut -c 1-12)" + - name: Create URL + id: url + run: | + echo "::set-output name=url::https://nedbat.github.io/coverage-reports/reports/${{ steps.slug.outputs.slug }}/htmlcov" + - name: Pushes to another repository uses: sebastian-palma/github-action-push-to-another-repository@allow-creating-destination-directory env: @@ -133,13 +138,25 @@ jobs: source-directory: 'htmlcov' destination-github-username: 'nedbat' destination-repository-name: 'coverage-reports' - destination-repository-directory: 'reports/${{ steps.info.outputs.slug }}' + destination-repository-directory: 'reports/${{ steps.slug.outputs.slug }}' empty-repository: false create-destination-directory: true target-branch: main commit-message: "Coverage report for ${{ github.sha }}" 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 "https://nedbat.github.io/coverage-reports/reports/${{ steps.info.outputs.slug }}/htmlcov" + echo "Coverage report: ${{ steps.url.outputs.url }}" From cc5d85741ab33a84adb33400ecfcccd70e449a4a Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 21 Feb 2021 22:15:01 -0500 Subject: [PATCH 43/67] refactor: slightly better coverage in coveragetest --- metacov.ini | 2 +- tests/coveragetest.py | 6 +++--- tests/helpers.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/metacov.ini b/metacov.ini index 1ec94fda7..53b9f4098 100644 --- a/metacov.ini +++ b/metacov.ini @@ -78,7 +78,7 @@ partial_branches = if .* env.IRONPYTHON ignore_errors = true -precision = 1 +precision = 2 [paths] source = diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 8e5f2b0a5..9b7fe1676 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -125,7 +125,7 @@ def _check_arcs(self, a1, a2, arc_type): 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`. @@ -154,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. diff --git a/tests/helpers.py b/tests/helpers.py index 1348aad69..a96b793ef 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -198,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) From cc2afd8ea9c3aed22af29b2c14484230f46732e7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 22 Feb 2021 19:53:59 -0500 Subject: [PATCH 44/67] test: add tests of the failure asserts from check_coverage This brings the coverage of tests/coveragetest.py to 100%. --- tests/coveragetest.py | 6 ++--- tests/test_testing.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 9b7fe1676..3363fa894 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -55,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. @@ -115,8 +115,8 @@ def _check_arcs(self, a1, a2, arc_type): s1 = arcs_to_arcz_repr(a1) s2 = arcs_to_arcz_repr(a2) if s1 != s2: - lines1 = s1.splitlines(keepends=True) - lines2 = s2.splitlines(keepends=True) + 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: diff --git a/tests/test_testing.py b/tests/test_testing.py index 67ec8dff4..f5d9f9421 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -245,6 +245,57 @@ def test_detect_duplicate(self): 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"), From ec9668269897631a263b71fb9727555570e9f24f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 24 Feb 2021 13:23:22 -0500 Subject: [PATCH 45/67] test: oops, accidentally always skipped this test --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 66f471c3c..391a52e0b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -763,7 +763,7 @@ def test_current(self): assert cur0 is cur3 -@pytest.mark.skip(not env.PYBEHAVIOR.namespaces_pep420, +@pytest.mark.skipif(not env.PYBEHAVIOR.namespaces_pep420, reason="Python before 3.3 doesn't have namespace packages" ) class NamespaceModuleTest(UsingModulesMixin, CoverageTest): From 84cbf083a18a814c501eecb450d7ac4126e74054 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 24 Feb 2021 15:15:18 -0500 Subject: [PATCH 46/67] build: nicer publishing of HTML report Also, this correctly combines results from different runners. --- .github/workflows/coverage.yml | 65 ++++++++++++++++++++++++---------- coverage/cmdline.py | 2 +- igor.py | 6 +++- metacov.ini | 5 ++- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 60ae9e037..632babfa3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,7 +5,8 @@ name: "Coverage" on: push: - branches: ["master"] + branches: + - master # as currently structured, this adds too many jobs (checks?), so don't run it # on pull requests yet. #pull_request: @@ -40,6 +41,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" @@ -65,7 +68,15 @@ jobs: run: | set -xe python -m tox - python -m igor combine_html + + - 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" @@ -95,7 +106,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 @@ -105,9 +115,15 @@ jobs: name: metacov - name: "Combine and report" + id: combine + env: + COVERAGE_RCFILE: "metacov.ini" + COVERAGE_METAFILE: ".metacov" run: | set -xe 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 @@ -120,17 +136,24 @@ jobs: name: html_report path: htmlcov - - name: Create slug - id: slug - run: | - echo "::set-output name=slug::$(date +'%Y%m%d')_$(echo ${{github.sha}} | cut -c 1-12)" + - name: "Upload JSON report" + uses: actions/upload-artifact@v2 + with: + name: json_report + path: coverage.json - - name: Create URL - id: url + - name: "Create info for pushing to report repo" + id: info run: | - echo "::set-output name=url::https://nedbat.github.io/coverage-reports/reports/${{ steps.slug.outputs.slug }}/htmlcov" - - - name: Pushes to another repository + 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 }} @@ -138,17 +161,23 @@ jobs: source-directory: 'htmlcov' destination-github-username: 'nedbat' destination-repository-name: 'coverage-reports' - destination-repository-directory: 'reports/${{ steps.slug.outputs.slug }}' + destination-repository-directory: 'reports/${{ steps.info.outputs.slug }}' empty-repository: false create-destination-directory: true target-branch: main - commit-message: "Coverage report for ${{ github.sha }}" + 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 + - name: "Create redirection HTML file" run: | echo "" > coverage-report-redirect.html - echo "" >> coverage-report-redirect.html + echo "" >> coverage-report-redirect.html echo "Coverage report redirect..." >> coverage-report-redirect.html - name: "Upload HTML redirect" @@ -157,6 +186,6 @@ jobs: name: coverage-report-redirect.html path: coverage-report-redirect.html - - name: Show link to report + - name: "Show link to report" run: | - echo "Coverage report: ${{ steps.url.outputs.url }}" + echo "Coverage report: ${{ steps.info.outputs.url }}" diff --git a/coverage/cmdline.py b/coverage/cmdline.py index cdcde4510..0be0cca19 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -771,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/igor.py b/igor.py index d8d2069d8..a4a460c00 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 diff --git a/metacov.ini b/metacov.ini index 53b9f4098..b3dffc3ee 100644 --- a/metacov.ini +++ b/metacov.ini @@ -77,7 +77,6 @@ partial_branches = if .* env.JYTHON if .* env.IRONPYTHON -ignore_errors = true precision = 2 [paths] @@ -87,3 +86,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 From 6c802eabcc79c0995ea7bde307527cf13f5f6a8c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 24 Feb 2021 21:01:55 -0500 Subject: [PATCH 47/67] build: make yaml syntax more uniform --- .github/workflows/cancel.yml | 4 +-- .github/workflows/coverage.yml | 5 ++-- .github/workflows/kit.yml | 43 ++++++++++++++++++--------------- .github/workflows/quality.yml | 9 ++++--- .github/workflows/testsuite.yml | 5 ++-- 5 files changed, 35 insertions(+), 31 deletions(-) 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 632babfa3..447464eb5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -4,12 +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 - # as currently structured, this adds too many jobs (checks?), so don't run it - # on pull requests yet. - #pull_request: workflow_dispatch: defaults: 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 25fcd73d8..24bd926ee 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: From c6fbb3888e6f05b6265ba9b30f5ea2444d0d51a7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 25 Feb 2021 07:04:33 -0500 Subject: [PATCH 48/67] build: run on Python 3.10 alpha 5 This required pinning setuptools more aggressively, and cleaning up some dependency sloppinesss. --- .github/workflows/coverage.yml | 5 +++++ .github/workflows/testsuite.yml | 5 +++++ requirements/ci.pip | 2 ++ requirements/dev.pip | 3 +++ requirements/pins.pip | 4 ++++ requirements/pytest.pip | 3 --- requirements/wheel.pip | 7 ++++--- tox.ini | 2 ++ 8 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 447464eb5..61ec9865e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,9 +27,12 @@ jobs: - 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" exclude: # Windows PyPy doesn't seem to work? @@ -58,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/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index 24bd926ee..a88bfba4c 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -26,12 +26,15 @@ jobs: - 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? @@ -58,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/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 608df1cfa..3bfb527f4 100644 --- a/requirements/dev.pip +++ b/requirements/dev.pip @@ -23,3 +23,6 @@ readme_renderer==26.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/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 From 88c584f0bafb7d5356a740ec684cfea7e515089e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 25 Feb 2021 19:14:52 -0500 Subject: [PATCH 49/67] refactor: simplify a one-iteration loop --- tests/test_html.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_html.py b/tests/test_html.py index ee2eb5755..51e0b93cd 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1078,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): From 2232990fbb6b0645a0af3935501f258cf89b37c6 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 25 Feb 2021 19:18:17 -0500 Subject: [PATCH 50/67] test: lines with `if not env.METACOV` won't be metacovered --- metacov.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/metacov.ini b/metacov.ini index b3dffc3ee..7045338e6 100644 --- a/metacov.ini +++ b/metacov.ini @@ -74,6 +74,7 @@ partial_branches = pragma: if failure pragma: part started if env.TESTING: + if not env.METACOV if .* env.JYTHON if .* env.IRONPYTHON From 1bdf48368c37bfe2de866e51133b240fda6eda36 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 25 Feb 2021 19:42:15 -0500 Subject: [PATCH 51/67] refactor: put a test in a more appropriate class --- tests/test_config.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 04503f309..f400fc173 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -425,17 +425,6 @@ def test_unknown_option_in_other_ini_file(self): 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", """\ - [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() - class ConfigFileTest(UsingModulesMixin, CoverageTest): """Tests of the config file settings in particular.""" @@ -702,6 +691,17 @@ def test_nocoveragerc_file_when_specified(self): 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'): From 198a11535b5065416c41e7fdc025718c1a92eff3 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 25 Feb 2021 20:32:34 -0500 Subject: [PATCH 52/67] test: add a test of missing sections and options --- coverage/config.py | 4 ++-- tests/test_config.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/coverage/config.py b/coverage/config.py index 026f8645e..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) diff --git a/tests/test_config.py b/tests/test_config.py index f400fc173..b1611c1b8 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,6 +10,7 @@ import pytest import coverage +from coverage.config import HandyConfigParser from coverage.misc import CoverageException from tests.coveragetest import CoverageTest, UsingModulesMixin @@ -425,6 +426,17 @@ def test_unknown_option_in_other_ini_file(self): with pytest.raises(CoverageException, match=msg): _ = coverage.Coverage() + def test_exceptions_from_missing_things(self): + self.make_file("config.ini", """\ + [run] + branch = True + """) + 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): """Tests of the config file settings in particular.""" From f3338803b7b8b4cda34270c4fbf951ad74913980 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 26 Feb 2021 12:01:45 -0500 Subject: [PATCH 53/67] build: update to latest pylint --- coverage/misc.py | 2 +- coverage/multiproc.py | 2 +- igor.py | 2 +- requirements/dev.pip | 4 ++-- tests/test_process.py | 2 +- tests/test_summary.py | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) 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/igor.py b/igor.py index a4a460c00..3c6afa667 100644 --- a/igor.py +++ b/igor.py @@ -387,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/requirements/dev.pip b/requirements/dev.pip index 3bfb527f4..791a2faed 100644 --- a/requirements/dev.pip +++ b/requirements/dev.pip @@ -14,8 +14,8 @@ pluggy==0.13.1 # for linting. greenlet==0.4.16 -astroid==2.4.2 -pylint==2.6.0 +astroid==2.5 +pylint==2.7.1 check-manifest==0.46 readme_renderer==26.0 diff --git a/tests/test_process.py b/tests/test_process.py index 01fd1eb81..9f07b9ccb 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1495,7 +1495,7 @@ def test_subprocess_with_pth_files(self): 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: assert f.read() == "Hello, world!\n" diff --git a/tests/test_summary.py b/tests/test_summary.py index 0632c8f50..36f3885c6 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -677,7 +677,7 @@ 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() assert "mybranch.py 5 5 2 0 0%" in report @@ -739,7 +739,7 @@ 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() @@ -766,7 +766,7 @@ def test_missing_py_file_during_run(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 # Put back the missing Python file. From cb647cd476a5b54893ac28b50d2f79910bedb96a Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 26 Feb 2021 16:34:45 -0500 Subject: [PATCH 54/67] refactor: remove unneeded code --- coverage/results.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/coverage/results.py b/coverage/results.py index ae8366bf5..7f9893618 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): From dc9b842271b49b85681987643fceab598bd4de21 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 26 Feb 2021 17:11:36 -0500 Subject: [PATCH 55/67] test: add tests of report sorting options --- tests/test_summary.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_summary.py b/tests/test_summary.py index 36f3885c6..3be1e8690 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -933,6 +933,16 @@ 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'" From c7052bb24da03b5112c6d7fd1386936e211223f0 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 27 Feb 2021 07:34:33 -0500 Subject: [PATCH 56/67] fix: HTML report makes room for 4-digit line numbers #1124 Fixes: #1124 --- CHANGES.rst | 4 ++++ coverage/htmlfiles/style.css | 10 +++++----- coverage/htmlfiles/style.scss | 6 +++--- tests/gold/html/styled/style.css | 10 +++++----- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0c5d76677..7eaf22a2e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,8 +29,12 @@ Unreleased they have been combined. This was requested in `issue 1108`_ and implemented in `pull request 1110`_. Thanks, Éric Larivière. +- The HTML report has a little more room for line numbers so that 4-digit + numbers work well, fixing `issue 1124`_. + .. _issue 1108: https://github.com/nedbat/coveragepy/issues/1108 .. _pull request 1110: https://github.com/nedbat/coveragepy/pull/1110 +.. _issue 1124: https://github.com/nedbat/coveragepy/issues/1124 .. _changes_54: 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/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; } } From 8750045b3dd2cd7530bb2795f2d7a36f89353225 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 27 Feb 2021 15:44:20 -0500 Subject: [PATCH 57/67] fix: HTML line visibility is saved in local storage #1123 Seems like we could unify the two different uses of localStorage, but that's for another time. Fixes: #1123 --- CHANGES.rst | 11 ++++++-- coverage/htmlfiles/coverage_html.js | 43 +++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7eaf22a2e..201f1d069 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,13 +29,20 @@ Unreleased they have been combined. This was requested in `issue 1108`_ and implemented in `pull request 1110`_. Thanks, Éric Larivière. -- The HTML report has a little more room for line numbers so that 4-digit - numbers work well, fixing `issue 1124`_. +- 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`_. .. _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 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. From f5d60436c51a47629d4afee8aa97ec69e5eeb9c7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 07:31:16 -0500 Subject: [PATCH 58/67] refactor: remove unused exception handling --- coverage/summary.py | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) 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): From 3f3856702d7e88065dd0e13f022a4d7c69eeaf6b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 08:02:28 -0500 Subject: [PATCH 59/67] build: coverage ci should fail fast The whole point of the coverage workflow is to combine the results at the end. If one job fails, stop everything. --- .github/workflows/coverage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 61ec9865e..ee798ada1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -38,7 +38,8 @@ jobs: # Windows PyPy doesn't seem to work? - os: windows-latest python-version: "pypy3" - fail-fast: false + # If one job fails, stop the whole thing. + fail-fast: true steps: - name: "Check out the repo" From 169972546b699f01813131d5356d0edcfc739343 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 08:28:58 -0500 Subject: [PATCH 60/67] fix: improve an error message. #803 Fixes #803. --- CHANGES.rst | 4 ++++ coverage/sqldata.py | 4 ++-- tests/test_data.py | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 201f1d069..82f174dd3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -37,6 +37,10 @@ Unreleased - 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 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 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/tests/test_data.py b/tests/test_data.py index fe37bd9e7..eac9c36fa 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -159,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 pytest.raises(CoverageException, match="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 pytest.raises(CoverageException, match="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): From fd78a6d8135b448235bdfdc61b5d6f41367f537f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 09:01:31 -0500 Subject: [PATCH 61/67] docs: correct the GitHub action badges --- README.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 From b12f189e8ed34b31438b8cca19133b74f7d67f90 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 09:02:01 -0500 Subject: [PATCH 62/67] test: fix a few metacov exclusions --- coverage/backward.py | 2 +- metacov.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coverage/backward.py b/coverage/backward.py index 3b21fc99f..ac781ab96 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -78,7 +78,7 @@ try: import reprlib -except ImportError: # pragma: part covered +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 diff --git a/metacov.ini b/metacov.ini index 7045338e6..47ed31344 100644 --- a/metacov.ini +++ b/metacov.ini @@ -50,6 +50,7 @@ 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 @@ -74,7 +75,6 @@ partial_branches = pragma: if failure pragma: part started if env.TESTING: - if not env.METACOV if .* env.JYTHON if .* env.IRONPYTHON From 79087b9f9e561bec1654ee80f143c4754641e81f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 10:46:11 -0500 Subject: [PATCH 63/67] fix: don't report branches to missing lines. #1065 Fixes: #1065 Fixes: #955 --- CHANGES.rst | 8 ++++++++ coverage/results.py | 2 +- tests/test_coverage.py | 10 +++++----- tests/test_summary.py | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 82f174dd3..1dd57b034 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,12 @@ Unreleased 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 @@ -41,6 +47,8 @@ Unreleased 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 diff --git a/coverage/results.py b/coverage/results.py index 7f9893618..4916864df 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -312,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/tests/test_coverage.py b/tests/test_coverage.py index 00648c189..30a8edc5a 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -732,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; @@ -744,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; @@ -756,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): @@ -768,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; @@ -778,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): diff --git a/tests/test_summary.py b/tests/test_summary.py index 3be1e8690..8596c45c4 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -280,7 +280,7 @@ 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%', ] From c842085d0cfcce61a8a39d6deeecfd91aa19abd3 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 12:22:06 -0500 Subject: [PATCH 64/67] doc: latest sample HTML report --- doc/sample_html/cogapp___init___py.html | 4 +- doc/sample_html/cogapp___main___py.html | 4 +- doc/sample_html/cogapp_backward_py.html | 4 +- doc/sample_html/cogapp_cogapp_py.html | 4 +- doc/sample_html/cogapp_makefiles_py.html | 4 +- doc/sample_html/cogapp_test_cogapp_py.html | 4 +- doc/sample_html/cogapp_test_makefiles_py.html | 4 +- .../cogapp_test_whiteutils_py.html | 4 +- doc/sample_html/cogapp_whiteutils_py.html | 4 +- doc/sample_html/coverage_html.js | 43 +++++++++++++++---- doc/sample_html/index.html | 4 +- doc/sample_html/status.json | 2 +- doc/sample_html/style.css | 10 ++--- 13 files changed, 61 insertions(+), 34 deletions(-) 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; } } From 9e8010cf1626dad26a02aa949c105481d540c631 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 12:22:57 -0500 Subject: [PATCH 65/67] build: version 5.5 prep --- CHANGES.rst | 6 ++++-- coverage/version.py | 2 +- doc/conf.py | 6 +++--- doc/index.rst | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1dd57b034..afd5f16ae 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,8 +21,10 @@ want to know what's different in 5.0 since 4.5.x, see :ref:`whatsnew5x`. .. Version 9.8.1 --- 2027-07-27 .. ---------------------------- -Unreleased ----------- +.. _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 diff --git a/coverage/version.py b/coverage/version.py index eb4e36b1b..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, 5, 0, "alpha", 0) +version_info = (5, 5, 0, "final", 0) def _make_version(major, minor, micro, releaselevel, serial): diff --git a/doc/conf.py b/doc/conf.py index b76c0a235..770396bcf 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -66,11 +66,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/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/ From 44321223f4d980c12e80a381d8d489bb19f89a33 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 28 Feb 2021 12:28:59 -0500 Subject: [PATCH 66/67] build: a better kit-building link --- howto.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/howto.txt b/howto.txt index 6ecea7cf3..aae6c47d1 100644 --- a/howto.txt +++ b/howto.txt @@ -45,7 +45,7 @@ $ make publish - Kits: - Manually trigger the kit GitHub Action - - https://github.com/nedbat/coveragepy/actions?query=workflow%3A%22Build+kits%22 + - 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. From f06b950c318bcd9cf8bc8ec3a03fb4d0d518b8d9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 8 Feb 2021 07:07:04 -0500 Subject: [PATCH 67/67] docs: use sphinxcontrib for the rst builder --- doc/conf.py | 3 ++- doc/requirements.pip | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 770396bcf..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', diff --git a/doc/requirements.pip b/doc/requirements.pip index bb875ca4e..f1f01c66d 100644 --- a/doc/requirements.pip +++ b/doc/requirements.pip @@ -5,10 +5,7 @@ doc8==0.8.1 pyenchant==3.2.0 sphinx==3.4.3 -#sphinx-rst-builder==0.0.3 -# fails, -# fixed with https://github.com/davidfritzsche/sphinx-rst-builder/pull/3 -git+https://github.com/nedbat/sphinx-rst-builder +sphinxcontrib-restbuilder==0.3 sphinxcontrib-spelling==7.1.0 sphinx_rtd_theme==0.5.1 sphinx-autobuild==2020.9.1