summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2023-02-12 09:27:25 -0500
committerNed Batchelder <ned@nedbatchelder.com>2023-02-12 11:52:05 -0500
commitf77be1770a9d93ed69d6b5a26dcbe5dbfe14e380 (patch)
tree57ee04342aef1966e9a73563306f5dceb415b6a5
parent6bc043981f6548852844ea6b16d5ef7d37c0417d (diff)
downloadpython-coveragepy-git-f77be1770a9d93ed69d6b5a26dcbe5dbfe14e380.tar.gz
fix: Path objects are ok for data_file and config_file. #1552
-rw-r--r--CHANGES.rst7
-rw-r--r--coverage/control.py10
-rw-r--r--coverage/sqldata.py4
-rw-r--r--coverage/types.py13
-rw-r--r--tests/test_api.py12
-rw-r--r--tests/test_config.py8
-rw-r--r--tests/test_data.py9
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()