diff options
-rw-r--r-- | CHANGES.rst | 7 | ||||
-rw-r--r-- | coverage/control.py | 10 | ||||
-rw-r--r-- | coverage/sqldata.py | 4 | ||||
-rw-r--r-- | coverage/types.py | 13 | ||||
-rw-r--r-- | tests/test_api.py | 12 | ||||
-rw-r--r-- | tests/test_config.py | 8 | ||||
-rw-r--r-- | tests/test_data.py | 9 |
7 files changed, 45 insertions, 18 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index f336cff5..1c27501d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -27,6 +27,11 @@ Unreleased themselves with messages like "Wrote XML report to file.xml" before spewing a traceback about their failure. +- Fix: arguments in the public API that name file paths now accept pathlib.Path + objects. This includes the ``data_file`` and ``config_file`` arguments to + the Coverage constructor and the ``basename`` argument to CoverageData. + Closes `issue 1552`_. + - Fix: In some embedded environments, an IndexError could occur on stop() when the originating thread exits before completion. This is now fixed, thanks to `Russell Keith-Magee <pull 1543_>`_, closing `issue 1542`_. @@ -38,7 +43,7 @@ Unreleased .. _pull 1543: https://github.com/nedbat/coveragepy/pull/1543 .. _pull 1547: https://github.com/nedbat/coveragepy/pull/1547 .. _pull 1550: https://github.com/nedbat/coveragepy/pull/1550 - +.. _issue 1552: https://github.com/nedbat/coveragepy/issues/1552 .. scriv-start-here diff --git a/coverage/control.py b/coverage/control.py index cdea42ee..e385f0e2 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -47,7 +47,7 @@ from coverage.report import render_report from coverage.results import Analysis from coverage.summary import SummaryReporter from coverage.types import ( - TConfigurable, TConfigSectionIn, TConfigValueIn, TConfigValueOut, + FilePath, TConfigurable, TConfigSectionIn, TConfigValueIn, TConfigValueOut, TFileDisposition, TLineNo, TMorf, ) from coverage.xmlreport import XmlReporter @@ -113,13 +113,13 @@ class Coverage(TConfigurable): def __init__( # pylint: disable=too-many-arguments self, - data_file: Optional[Union[str, DefaultValue]] = DEFAULT_DATAFILE, + data_file: Optional[Union[FilePath, DefaultValue]] = DEFAULT_DATAFILE, data_suffix: Optional[Union[str, bool]] = None, cover_pylib: Optional[bool] = None, auto_data: bool = False, timid: Optional[bool] = None, branch: Optional[bool] = None, - config_file: Union[str, bool] = True, + config_file: Union[FilePath, bool] = True, source: Optional[Iterable[str]] = None, source_pkgs: Optional[Iterable[str]] = None, omit: Optional[Union[str, Iterable[str]]] = None, @@ -227,6 +227,8 @@ class Coverage(TConfigurable): self._no_disk = data_file is None if isinstance(data_file, DefaultValue): data_file = None + if data_file is not None: + data_file = os.fspath(data_file) # This is injectable by tests. self._debug_file: Optional[IO[str]] = None @@ -267,6 +269,8 @@ class Coverage(TConfigurable): self._should_write_debug = True # Build our configuration from a number of sources. + if not isinstance(config_file, bool): + config_file = os.fspath(config_file) self.config = read_coverage_config( config_file=config_file, warn=self._warn, diff --git a/coverage/sqldata.py b/coverage/sqldata.py index bb26e4d5..15b30ae9 100644 --- a/coverage/sqldata.py +++ b/coverage/sqldata.py @@ -31,7 +31,7 @@ from coverage.exceptions import CoverageException, DataError from coverage.files import PathAliases from coverage.misc import file_be_gone, isolate_module from coverage.numbits import numbits_to_nums, numbits_union, nums_to_numbits -from coverage.types import TArc, TDebugCtl, TLineNo, TWarnFn +from coverage.types import FilePath, TArc, TDebugCtl, TLineNo, TWarnFn from coverage.version import __version__ os = isolate_module(os) @@ -216,7 +216,7 @@ class CoverageData(AutoReprMixin): def __init__( self, - basename: Optional[str] = None, + basename: Optional[FilePath] = None, suffix: Optional[Union[str, bool]] = None, no_disk: bool = False, warn: Optional[TWarnFn] = None, diff --git a/coverage/types.py b/coverage/types.py index b8135d05..e01f451e 100644 --- a/coverage/types.py +++ b/coverage/types.py @@ -7,9 +7,12 @@ Types for use throughout coverage.py. from __future__ import annotations +import os +import pathlib + from types import FrameType, ModuleType from typing import ( - Any, Callable, Dict, Iterable, List, Mapping, Optional, Set, Tuple, Union, + Any, Callable, Dict, Iterable, List, Mapping, Optional, Set, Tuple, Type, Union, TYPE_CHECKING, ) @@ -23,6 +26,14 @@ else: class Protocol: # pylint: disable=missing-class-docstring pass +## File paths + +# For arguments that are file paths: +FilePath = Union[str, os.PathLike] +# For testing FilePath arguments +FilePathClasses = [str, pathlib.Path] +FilePathType = Union[Type[str], Type[pathlib.Path]] + ## Python tracing class TTraceFn(Protocol): diff --git a/tests/test_api.py b/tests/test_api.py index 1c565421..596510eb 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -25,7 +25,7 @@ from coverage.data import line_counts, sorted_lines from coverage.exceptions import CoverageException, DataError, NoDataError, NoSource from coverage.files import abs_file, relative_filename from coverage.misc import import_local_file -from coverage.types import Protocol, TCovKwargs +from coverage.types import FilePathClasses, FilePathType, Protocol, TCovKwargs from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin from tests.goldtest import contains, doesnt_contain @@ -221,26 +221,28 @@ class ApiTest(CoverageTest): cov.save() self.assertFiles(["datatest1.py", ".coverage"]) - def test_datafile_specified(self) -> None: + @pytest.mark.parametrize("file_class", FilePathClasses) + def test_datafile_specified(self, file_class: FilePathType) -> None: # You can specify the data file name. self.make_file("datatest2.py", """\ fooey = 17 """) self.assertFiles(["datatest2.py"]) - cov = coverage.Coverage(data_file="cov.data") + cov = coverage.Coverage(data_file=file_class("cov.data")) self.start_import_stop(cov, "datatest2") cov.save() self.assertFiles(["datatest2.py", "cov.data"]) - def test_datafile_and_suffix_specified(self) -> None: + @pytest.mark.parametrize("file_class", FilePathClasses) + def test_datafile_and_suffix_specified(self, file_class: FilePathType) -> None: # You can specify the data file name and suffix. self.make_file("datatest3.py", """\ fooey = 17 """) self.assertFiles(["datatest3.py"]) - cov = coverage.Coverage(data_file="cov.data", data_suffix="14") + cov = coverage.Coverage(data_file=file_class("cov.data"), data_suffix="14") self.start_import_stop(cov, "datatest3") cov.save() self.assertFiles(["datatest3.py", "cov.data.14"]) diff --git a/tests/test_config.py b/tests/test_config.py index d60a4eeb..2ee5eae0 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,8 +6,8 @@ from __future__ import annotations import sys - from unittest import mock + import pytest import coverage @@ -15,6 +15,7 @@ from coverage import Coverage from coverage.config import HandyConfigParser from coverage.exceptions import ConfigError, CoverageWarning from coverage.tomlconfig import TomlConfigParser +from coverage.types import FilePathClasses, FilePathType from tests.coveragetest import CoverageTest, UsingModulesMixin @@ -50,7 +51,8 @@ class ConfigTest(CoverageTest): assert not cov.config.branch assert cov.config.data_file == ".hello_kitty.data" - def test_named_config_file(self) -> None: + @pytest.mark.parametrize("file_class", FilePathClasses) + def test_named_config_file(self, file_class: FilePathType) -> None: # You can name the config file what you like. self.make_file("my_cov.ini", """\ [run] @@ -58,7 +60,7 @@ class ConfigTest(CoverageTest): ; I wouldn't really use this as a data file... data_file = delete.me """) - cov = coverage.Coverage(config_file="my_cov.ini") + cov = coverage.Coverage(config_file=file_class("my_cov.ini")) assert cov.config.timid assert not cov.config.branch assert cov.config.data_file == "delete.me" diff --git a/tests/test_data.py b/tests/test_data.py index 1cc64572..ab3f5f5b 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -24,7 +24,7 @@ from coverage.data import add_data_to_hash, line_counts from coverage.debug import DebugControlString from coverage.exceptions import DataError, NoDataError from coverage.files import PathAliases, canonical_filename -from coverage.types import TArc, TLineNo +from coverage.types import FilePathClasses, FilePathType, TArc, TLineNo from tests.coveragetest import CoverageTest from tests.helpers import assert_count_equal @@ -621,10 +621,13 @@ class CoverageDataTest(CoverageTest): class CoverageDataInTempDirTest(CoverageTest): """Tests of CoverageData that need a temporary directory to make files.""" - def test_read_write_lines(self) -> None: - covdata1 = DebugCoverageData("lines.dat") + @pytest.mark.parametrize("file_class", FilePathClasses) + def test_read_write_lines(self, file_class: FilePathType) -> None: + self.assert_doesnt_exist("lines.dat") + covdata1 = DebugCoverageData(file_class("lines.dat")) covdata1.add_lines(LINES_1) covdata1.write() + self.assert_exists("lines.dat") covdata2 = DebugCoverageData("lines.dat") covdata2.read() |