summaryrefslogtreecommitdiff
path: root/tests/config/test_sets.py
blob: f739b074f3caab155d1e79f64a0efc42456ffcd5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
from __future__ import annotations

from collections import OrderedDict
from pathlib import Path
from typing import Callable, Dict, Optional, Set, TypeVar

import pytest
from pytest_mock import MockerFixture

from tests.conftest import ToxIniCreator
from tox.config.cli.parser import Parsed
from tox.config.loader.memory import MemoryLoader
from tox.config.main import Config
from tox.config.sets import ConfigSet, EnvConfigSet
from tox.config.source.api import Section
from tox.pytest import ToxProjectCreator

ConfBuilder = Callable[[str], ConfigSet]


@pytest.fixture(name="conf_builder")
def _conf_builder(tox_ini_conf: ToxIniCreator) -> ConfBuilder:  # noqa: PT005
    def _make(conf_str: str) -> ConfigSet:
        return tox_ini_conf(f"[tox]\nenvlist=py39\n[testenv]\n{conf_str}").get_env("py39")

    return _make


def test_config_str(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("deps-x = 1\n    other: 2")
    config_set.add_config(keys="deps-x", of_type=str, default="", desc="ok")
    result = config_set["deps-x"]
    assert result == "1"


def test_config_path(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("path = path")
    config_set.add_config(keys="path", of_type=Path, default=Path(), desc="path")
    path_materialize = config_set["path"]
    assert path_materialize == Path("path")


def test_config_set(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("set = 1\n 2\n 3")
    config_set.add_config(keys="set", of_type=Set[int], default=set(), desc="set")
    set_materialize = config_set["set"]
    assert set_materialize == {1, 2, 3}


def test_config_optional_none(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("")
    config_set.add_config(
        keys="optional_none",
        of_type=Optional[int],  # type: ignore[arg-type]
        default=None,
        desc="optional_none",
    )
    optional_none = config_set["optional_none"]
    assert optional_none is None


def test_config_dict(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("dict = a=1\n  b=2\n  c=3")
    config_set.add_config(keys="dict", of_type=Dict[str, int], default={}, desc="dict")
    dict_val = config_set["dict"]
    assert dict_val == OrderedDict([("a", 1), ("b", 2), ("c", 3)])


def test_config_bad_type(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("crazy = something-bad")

    config_set.add_config(keys="crazy", of_type=TypeVar, default=TypeVar("V"), desc="crazy")
    with pytest.raises(TypeError) as context:
        assert config_set["crazy"]
    assert str(context.value) == f"something-bad cannot cast to {TypeVar!r}"


def test_config_bad_dict(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("bad_dict = something")

    config_set.add_config(keys="bad_dict", of_type=Dict[str, str], default={}, desc="bad_dict")
    with pytest.raises(TypeError) as context:
        assert config_set["bad_dict"]
    assert str(context.value) == "dictionary lines must be of form key=value, found 'something'"


def test_config_bad_bool(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("bad_bool = whatever")
    config_set.add_config(keys="bad_bool", of_type=bool, default=False, desc="bad_bool")
    with pytest.raises(TypeError) as context:
        assert config_set["bad_bool"]
    error = "value 'whatever' cannot be transformed to bool, valid: , 0, 1, false, no, off, on, true, yes"
    assert str(context.value) == error


def test_config_constant(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("")
    config_set.add_constant(keys="a", value=1, desc="ok")
    const = config_set["a"]
    assert const == 1


def test_config_lazy_constant(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("")
    config_set.add_constant(keys="b", value=lambda: 2, desc="ok")
    lazy_const = config_set["b"]
    assert lazy_const == 2


def test_config_constant_repr(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("")
    defined = config_set.add_constant(keys="a", value=1, desc="ok")
    assert repr(defined)


def test_config_dynamic_repr(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("path = path")
    defined = config_set.add_config(keys="path", of_type=Path, default=Path(), desc="path")
    assert repr(defined)


def test_config_redefine_constant_fail(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("path = path")
    config_set.add_constant(keys="path", desc="desc", value="value")
    with pytest.raises(ValueError, match="config path already defined"):
        config_set.add_constant(keys="path", desc="desc2", value="value")


def test_config_redefine_dynamic_fail(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("path = path")
    config_set.add_config(keys="path", of_type=str, default="default_1", desc="path")
    with pytest.raises(ValueError, match="config path already defined"):
        config_set.add_config(keys="path", of_type=str, default="default_2", desc="path")


def test_config_dynamic_not_equal(conf_builder: ConfBuilder) -> None:
    config_set = conf_builder("")
    path = config_set.add_config(keys="path", of_type=Path, default=Path(), desc="path")
    paths = config_set.add_config(keys="paths", of_type=Path, default=Path(), desc="path")
    assert path != paths


def test_define_custom_set(tox_project: ToxProjectCreator) -> None:
    class MagicConfigSet(ConfigSet):

        SECTION = Section(None, "magic")

        def register_config(self) -> None:
            self.add_config("a", of_type=int, default=0, desc="number")
            self.add_config("b", of_type=str, default="", desc="string")

    project = tox_project({"tox.ini": "[testenv]\npackage=skip\n[A]\na=1\n[magic]\nb = ok"})
    result = project.run()
    section = MagicConfigSet.SECTION
    conf = result.state.conf.get_section_config(section, base=["A"], of_type=MagicConfigSet, for_env=None)
    assert conf["a"] == 1
    assert conf["b"] == "ok"
    exp = "MagicConfigSet(loaders=[IniLoader(section=magic, overrides={}), " "IniLoader(section=A, overrides={})])"
    assert repr(conf) == exp

    assert isinstance(result.state.conf._options, Parsed)


def test_do_not_allow_create_config_set(mocker: MockerFixture) -> None:
    with pytest.raises(TypeError, match="Can't instantiate"):
        ConfigSet(mocker.create_autospec(Config))  # type: ignore # the type checker also warns that ABC


def test_set_env_raises_on_non_str(mocker: MockerFixture) -> None:
    env_set = EnvConfigSet(mocker.create_autospec(Config), Section("a", "b"), "b")
    env_set.loaders.insert(0, MemoryLoader(set_env=1))
    with pytest.raises(TypeError, match="1"):
        assert env_set["set_env"]


@pytest.mark.parametrize("work_dir", ["a", ""])
def test_config_work_dir(tox_project: ToxProjectCreator, work_dir: str) -> None:
    project = tox_project({"tox.ini": "[tox]\ntoxworkdir=b"})
    result = project.run("c", *(["--workdir", str(project.path / work_dir)] if work_dir else []))
    expected = project.path / work_dir if work_dir else Path("b")
    assert expected == result.state.conf.core["work_dir"]