Skip to content

Commit

Permalink
fix: Path objects are ok for data_file and config_file. #1552
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Feb 12, 2023
1 parent 6bc0439 commit 164288e
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 13 deletions.
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Unreleased
themselves with messages like "Wrote XML report to file.xml" before spewing a
traceback about their failure.

- Fix: The ``data_file`` and ``config_file`` arguments to the Coverage
constructor now accept pathlib.Path objects, closing `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`_.
Expand All @@ -38,7 +41,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
Expand Down
10 changes: 7 additions & 3 deletions coverage/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
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
Expand Down Expand Up @@ -113,13 +113,13 @@ def current(cls) -> Optional[Coverage]:

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,
Expand Down Expand Up @@ -227,6 +227,8 @@ def __init__( # pylint: disable=too-many-arguments
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
Expand Down Expand Up @@ -267,6 +269,8 @@ def __init__( # pylint: disable=too-many-arguments
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,
Expand Down
13 changes: 12 additions & 1 deletion coverage/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

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,
)

Expand All @@ -23,6 +26,14 @@
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):
Expand Down
12 changes: 7 additions & 5 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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
Expand Down Expand Up @@ -221,26 +221,28 @@ def test_datafile_default(self) -> None:
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"])
Expand Down
8 changes: 5 additions & 3 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
from __future__ import annotations

import sys

from unittest import mock

import pytest

import coverage
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

Expand Down Expand Up @@ -50,15 +51,16 @@ def test_config_file(self) -> None:
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]
timid = True
; 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"
Expand Down

0 comments on commit 164288e

Please sign in to comment.