Skip to content

Commit

Permalink
Test write_config function
Browse files Browse the repository at this point in the history
  • Loading branch information
pvandyken committed Jan 12, 2024
1 parent fde43dd commit 969ea27
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 15 deletions.
6 changes: 4 additions & 2 deletions snakebids/io/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations
import errno

import json
from pathlib import Path
Expand Down Expand Up @@ -28,8 +29,9 @@ def write_config(
"""
config_file = Path(config_file)
if (config_file.exists()) and not force_overwrite:
err = FileExistsError(f"'{config_file}' already exists")
err.filename = str(config_file)
err = FileExistsError(
errno.EEXIST, f"'{config_file}' already exists", str(config_file)
)
raise err
config_file.parent.mkdir(exist_ok=True)

Expand Down
61 changes: 59 additions & 2 deletions snakebids/tests/test_yaml.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,73 @@
from __future__ import annotations

from io import StringIO
from pathlib import Path

import pytest
from hypothesis import given
from hypothesis import strategies as st
from pyfakefs.fake_filesystem import FakeFilesystem
from pytest_mock import MockerFixture
from pytest_mock.plugin import MockType

from snakebids.io.yaml import get_yaml_io
import snakebids.io.config as configio
import snakebids.io.yaml as yamlio
from snakebids.tests.helpers import allow_function_scoped


@given(path=st.text(min_size=1).map(Path))
def test_paths_formatted_as_str(path: Path):
string = StringIO()
yaml = get_yaml_io()
yaml = yamlio.get_yaml_io()
yaml.dump({"key": path}, string)
string.seek(0, 0)
assert yaml.load(string)["key"] == str(path)


class TestWriteConfig:
def io_mocks(self, mocker: MockerFixture) -> dict[str, MockType]:
return {
"mopen": mocker.patch.object(configio, "open", mocker.mock_open()),
"jsondump": mocker.patch.object(configio.json, "dump"),
"mkdir": mocker.patch.object(configio.Path, "mkdir"),
"yamldump": mocker.patch.object(yamlio.YAML, "dump"),
}

@allow_function_scoped
@given(
ext=st.sampled_from([".json", ".yaml", ".yml"]),
path=st.text().map(Path).filter(lambda p: p != Path() and not p.exists()),
)
def test_writes_correct_format(self, ext: str, path: Path, mocker: MockerFixture):
mocker.stopall()
mocks = self.io_mocks(mocker)
path = path.with_suffix(ext)
configio.write_config(path, {})
if ext == ".json":
mocks["mopen"].assert_called_once_with(path, "w", encoding="utf-8")
mocks["jsondump"].assert_called_once()
mocks["yamldump"].assert_not_called()
else:
mocks["mopen"].assert_not_called()
mocks["jsondump"].assert_not_called()
mocks["yamldump"].assert_called_once_with({}, path)

@allow_function_scoped
@given(path=st.text().filter(lambda s: s not in {".", ""}))
def test_doesnt_overwrite_file(self, path: str, fakefs: FakeFilesystem):
fakefs.reset()
fakefs.create_file(path)
with pytest.raises(FileExistsError, match="already exists"):
configio.write_config(path, {})

@allow_function_scoped
@given(path=st.text().filter(lambda s: s not in {".", ""}))
def test_overwrites_file_if_forced(
self, path: str, fakefs: FakeFilesystem, mocker: MockerFixture
):
mocker.stopall()
fakefs.reset()
fakefs.create_file(path)
mocks = self.io_mocks(mocker)
configio.write_config(path, {}, force_overwrite=True)
assert mocks["jsondump"].call_count ^ mocks["yamldump"].call_count
8 changes: 4 additions & 4 deletions typings/pyfakefs/fake_filesystem.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class FakeFilesystem:
"""Set the simulated type of operating system underlying the fake
file system."""
...
def reset(self, total_size: int | None = ...): # -> None:
def reset(self, total_size: int | None = ...) -> None:
"""Remove all file system contents and reset the root."""
...
def pause(self) -> None:
Expand Down Expand Up @@ -352,7 +352,7 @@ class FakeFilesystem:
times: tuple[int | float, int | float] | None = ...,
*,
ns: tuple[int, int] | None = ...,
follow_symlinks: bool = ...
follow_symlinks: bool = ...,
) -> None:
"""Change the access and modified times of a file.
Expand Down Expand Up @@ -724,15 +724,15 @@ class FakeFilesystem:
...
def create_file(
self,
file_path: AnyPath,
file_path: AnyPath[AnyStr],
st_mode: int = ...,
contents: AnyString = ...,
st_size: int | None = ...,
create_missing_dirs: bool = ...,
apply_umask: bool = ...,
encoding: str | None = ...,
errors: str | None = ...,
side_effect: Callable | None = ...,
side_effect: Callable[[FakeFile], Any] | None = ...,
) -> FakeFile:
"""Create `file_path`, including all the parent directories along
the way.
Expand Down
18 changes: 11 additions & 7 deletions typings/pyfakefs/helpers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ This type stub file was generated by pyright.

import io
import os
import platform
import sys
from typing import Any, AnyStr, Optional, Union, overload

"""Helper classes use for fake file system implementation."""
AnyString = Union[str, bytes]
AnyPath = Union[AnyStr, "os.PathLike[str]", "os.PathLike[bytes]"]
AnyPath = Union[AnyStr, "os.PathLike[AnyStr]"]
IS_PYPY = ...
IS_WIN = ...
IN_DOCKER = ...
Expand Down Expand Up @@ -58,12 +57,14 @@ def is_int_type(val: Any) -> bool:

def is_byte_string(val: Any) -> bool:
"""Return True if `val` is a bytes-like object, False for a unicode
string."""
string.
"""
...

def is_unicode_string(val: Any) -> bool:
"""Return True if `val` is a unicode string, False for a bytes-like
object."""
object.
"""
...

@overload
Expand All @@ -73,12 +74,14 @@ def make_string_path(dir_name: os.PathLike) -> str: ...
def make_string_path(dir_name: AnyPath) -> AnyStr: ...
def to_string(path: Union[AnyStr, Union[str, bytes]]) -> str:
"""Return the string representation of a byte string using the preferred
encoding, or the string itself if path is a str."""
encoding, or the string itself if path is a str.
"""
...

def to_bytes(path: Union[AnyStr, Union[str, bytes]]) -> bytes:
"""Return the bytes representation of a string using the preferred
encoding, or the byte string itself if path is a byte string."""
encoding, or the byte string itself if path is a byte string.
"""
...

def join_strings(s1: AnyStr, s2: AnyStr) -> AnyStr:
Expand All @@ -88,7 +91,8 @@ def join_strings(s1: AnyStr, s2: AnyStr) -> AnyStr:
def real_encoding(encoding: Optional[str]) -> Optional[str]:
"""Since Python 3.10, the new function ``io.text_encoding`` returns
"locale" as the encoding if None is defined. This will be handled
as no encoding in pyfakefs."""
as no encoding in pyfakefs.
"""
...

def now(): ...
Expand Down

0 comments on commit 969ea27

Please sign in to comment.