Skip to content

Commit

Permalink
Merge pull request #305 from tkkuehn/test-magic-args
Browse files Browse the repository at this point in the history
Test magic args
  • Loading branch information
tkkuehn authored Jun 13, 2023
2 parents ec0af2e + 1688e0d commit 05f1d9d
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 3 deletions.
1 change: 0 additions & 1 deletion snakebids/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ def _resolve_path(path_candidate: Any) -> Any:
If os.Pathlike or list of os.Pathlike, the same paths resolved.
Otherwise, the argument unchanged.
"""

if isinstance(path_candidate, Sequence) and not isinstance(path_candidate, str):
return [
_resolve_path(p) # type: ignore[reportUnknownArgumentType]
Expand Down
40 changes: 39 additions & 1 deletion snakebids/tests/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from snakebids.core.datasets import BidsComponent
from snakebids.core.input_generation import BidsDataset
from snakebids.tests import helpers
from snakebids.types import ZipList
from snakebids.types import InputConfig, InputsConfig, ZipList
from snakebids.utils.utils import BidsEntity, MultiSelectDict

_Ex_co = TypeVar("_Ex_co", bound=str, covariant=True)
Expand Down Expand Up @@ -51,6 +51,44 @@ def bids_entity_lists(
).filter(lambda v: v != ["datatype"])


@st.composite
def input_configs(draw: st.DrawFn) -> InputConfig:
filters = draw(
st.one_of(
st.dictionaries(
bids_entity().map(str),
st.one_of(st.booleans(), bids_value(), st.lists(bids_value())),
),
st.none(),
)
)

wildcards = draw(st.one_of(st.lists(bids_entity().map(str)), st.none()))
custom_path = draw(
st.one_of(
st.text(
alphabet=st.characters(
blacklist_categories=("Cs",), blacklist_characters=("\x00",)
)
),
st.none(),
)
)

pybids_inputs: InputConfig = {}
if wildcards is not None:
pybids_inputs.update({"wildcards": wildcards})
if filters is not None:
pybids_inputs.update({"filters": filters})
if custom_path is not None:
pybids_inputs.update({"custom_path": custom_path})
return pybids_inputs


def inputs_configs() -> st.SearchStrategy[InputsConfig]:
return st.dictionaries(st.text(min_size=1), input_configs())


@st.composite
def zip_lists( # noqa: PLR0913
draw: st.DrawFn,
Expand Down
43 changes: 42 additions & 1 deletion snakebids/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from __future__ import absolute_import
from __future__ import absolute_import, annotations

import copy
import json
from pathlib import Path
from typing import Any, cast

import hypothesis.strategies as st
import pytest
import snakemake
from hypothesis import HealthCheck, assume, example, given, settings
from pytest_mock.plugin import MockerFixture

from snakebids.app import update_config
from snakebids.cli import SnakebidsArgs
from snakebids.tests import strategies as sb_st
from snakebids.types import InputConfig, InputsConfig

from .. import app as sn_app
from ..app import SnakeBidsApp
Expand All @@ -33,6 +37,43 @@ def app(mocker: MockerFixture):
return app


class TestUpdateConfig:
@given(
input_config=sb_st.input_configs(),
)
def test_magic_args(
self,
input_config: InputConfig,
):
config_copy: dict[str, Any] = copy.deepcopy(config)
config_copy["bids_dir"] = "root"
config_copy["output_dir"] = "app"
args = SnakebidsArgs(
force=False,
outputdir=Path("app"),
snakemake_args=[],
args_dict={
"filter_bold": input_config.get("filters"),
"wildcards_bold": input_config.get("wildcards"),
"path_bold": input_config.get("custom_path"),
},
)
update_config(config_copy, args)
inputs_config: InputsConfig = cast(InputsConfig, config_copy["pybids_inputs"])
if "filters" in input_config:
for key, value in input_config["filters"].items():
assert inputs_config["bold"].get("filters", {}).get(key) == value
if "wildcards" in input_config:
assert set(input_config["wildcards"]) <= set(
inputs_config["bold"].get("wildcards", [])
)
if "custom_path" in input_config:
assert (
inputs_config["bold"].get("custom_path", "")
== Path(input_config["custom_path"]).resolve()
)


class TestRunSnakemake:
valid_chars = st.characters(
min_codepoint=48, max_codepoint=122, whitelist_categories=["Ll", "Lu"]
Expand Down
34 changes: 34 additions & 0 deletions snakebids/tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import copy
import itertools as it
import sys
from argparse import ArgumentParser, Namespace
from collections.abc import Sequence
Expand All @@ -9,6 +10,7 @@
from typing import Mapping

import pytest
from hypothesis import HealthCheck, given, settings
from pytest_mock.plugin import MockerFixture

from snakebids.cli import (
Expand All @@ -17,6 +19,8 @@
create_parser,
parse_snakebids_args,
)
from snakebids.tests import strategies as sb_st
from snakebids.types import InputsConfig

from .mock.config import parse_args, pybids_inputs

Expand Down Expand Up @@ -73,6 +77,36 @@ class TestAddDynamicArgs:
]
mock_all_args = mock_basic_args + mock_args_special

@given(sb_st.inputs_configs())
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
def test_dynamic_inputs(self, mocker: MockerFixture, pybids_inputs: InputsConfig):
p = create_parser()
add_dynamic_args(p, copy.deepcopy(parse_args), pybids_inputs)
magic_filters = list(
it.chain.from_iterable(
[[f"--filter-{key}", "entity=value"] for key in pybids_inputs]
)
)
magic_wildcards = list(
it.chain.from_iterable(
[[f"--wildcards-{key}", "test"] for key in pybids_inputs]
)
)
magic_path = list(
it.chain.from_iterable([[f"--path-{key}", "test"] for key in pybids_inputs])
)
mocker.patch.object(
sys,
"argv",
self.mock_all_args + magic_filters + magic_wildcards + magic_path,
)
args = parse_snakebids_args(p)
for key in pybids_inputs:
key_identifier = key.replace("-", "_") # argparse does this
assert isinstance(args.args_dict[f"path_{key_identifier}"], str)
assert isinstance(args.args_dict[f"filter_{key_identifier}"], dict)
assert isinstance(args.args_dict[f"wildcards_{key_identifier}"], list)

def test_fails_if_missing_arguments(
self, parser: ArgumentParser, mocker: MockerFixture
):
Expand Down

0 comments on commit 05f1d9d

Please sign in to comment.