File: test_toml_config.py

package info (click to toggle)
python-line-profiler 5.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,256 kB
  • sloc: python: 8,119; sh: 810; ansic: 297; makefile: 14
file content (147 lines) | stat: -rw-r--r-- 5,635 bytes parent folder | download
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
"""
Test the handling of TOML configs.
"""
import os
import pathlib
import pytest
import textwrap
from line_profiler.toml_config import ConfigSource


def write_text(path: pathlib.Path, text: str, /, *args, **kwargs) -> int:
    text = textwrap.dedent(text).strip('\n')
    return path.write_text(text, *args, **kwargs)


@pytest.fixture(autouse=True)
def fresh_curdir(monkeypatch: pytest.MonkeyPatch,
                 tmp_path_factory: pytest.TempPathFactory) -> pathlib.Path:
    """
    Ensure that the tests start on a clean slate: they shouldn't see
    the environment variable :envvar:`LINE_PROFILER_RC`, nor should the
    :py:meth:`~.ConfigSource.from_config` lookup find any config file.

    Yields:
        curdir (pathlib.Path):
            The temporary directory we `chdir()`-ed into for the test.
    """
    monkeypatch.delenv('LINE_PROFILER_RC', raising=False)
    path = tmp_path_factory.mktemp('clean').absolute()
    monkeypatch.chdir(path)
    yield path


def test_environment_isolation() -> None:
    """
    Test that we have isolated the tests from the environment with the
    :py:func:`fresh_curdir` fixture.
    """
    assert 'LINE_PROFILER_RC' not in os.environ
    assert ConfigSource.from_config() == ConfigSource.from_default()


def test_default_config_deep_copy() -> None:
    """
    Test that :py:meth:`ConfigSource.from_default` always return a fresh
    copy of the default config.
    """
    default_1, default_2 = (
        ConfigSource.from_default().conf_dict for _ in (1, 2))
    assert default_1 == default_2
    assert default_1 is not default_2
    # Sublist
    environ_flags = default_1['setup']['environ_flags']
    assert isinstance(environ_flags, list)
    assert environ_flags == default_2['setup']['environ_flags']
    assert environ_flags is not default_2['setup']['environ_flags']
    # Subtable
    column_widths = default_1['show']['column_widths']
    assert isinstance(column_widths, dict)
    assert column_widths == default_2['show']['column_widths']
    assert column_widths is not default_2['show']['column_widths']


def test_table_normalization(fresh_curdir: pathlib.Path) -> None:
    """
    Test that even if a config file misses some items (and has so extra
    ones), it is properly normalized to contain the same keys as the
    default table.
    """
    default_config = ConfigSource.from_default().conf_dict
    toml = fresh_curdir / 'foo.toml'
    write_text(toml, """
    [unrelated.table]
    foo = 'foo'  # This should be ignored

    [tool.line_profiler.write]
    output_prefix = 'my_prefix'  # This is parsed and retained
    nonexistent_key = 'nonexistent_value'  # This should be ignored
    """)
    loaded = ConfigSource.from_config(toml)
    assert loaded.path.samefile(toml)
    assert loaded.conf_dict['write']['output_prefix'] == 'my_prefix'
    assert 'nonexistent_key' not in loaded.conf_dict['write']
    del loaded.conf_dict['write']['output_prefix']
    del default_config['write']['output_prefix']
    assert loaded.conf_dict == default_config


def test_malformed_table(fresh_curdir: pathlib.Path) -> None:
    """
    Test that we get a `ValueError` when loading a malformed table with a
    non-subtable value taking the place of a supposed subtable.
    """
    toml = fresh_curdir / 'foo.toml'
    write_text(toml, """
    [tool.line_profiler]
    write = [{lprof = true}]  # This shouldn't be a list
    """)
    with pytest.raises(ValueError,
                       match=r"config = .*: expected .* keys.*:"
                       r".*'tool\.line_profiler\.write'"):
        ConfigSource.from_config(toml)


def test_config_lookup_hierarchy(monkeypatch: pytest.MonkeyPatch,
                                 fresh_curdir: pathlib.Path) -> None:
    """
    Test the hierarchy according to which we load config files.
    """
    default = ConfigSource.from_default().path
    # Lowest priority: `pyproject.toml` or `line_profiler.toml` in an
    # ancestral directory
    curdir = fresh_curdir
    lowest_priority = curdir / 'line_profiler.toml'
    lowest_priority.touch()
    curdir = curdir / 'child'
    curdir.mkdir()
    monkeypatch.chdir(curdir)
    assert ConfigSource.from_config().path.samefile(lowest_priority)
    # Higher priority: the same in the current directory
    # (`line_profiler.toml` preferred over `pyproject.toml`)
    lower_priority = curdir / 'pyproject.toml'
    lower_priority.touch()
    assert ConfigSource.from_config().path.samefile(lower_priority)
    low_priority = curdir / 'line_profiler.toml'
    low_priority.touch()
    assert ConfigSource.from_config().path.samefile(low_priority)
    # Higher priority: a file specified by the `${LINE_PROFILER_RC}`
    # environment variable (but we fall back to the default if that
    # fails)
    monkeypatch.setenv('LINE_PROFILER_RC', 'foo.toml')
    assert ConfigSource.from_config().path.samefile(default)
    high_priority = curdir / 'foo.toml'
    high_priority.touch()
    assert ConfigSource.from_config().path.samefile(high_priority)
    # Highest priority: a file passed explicitly to the `config`
    # parameter
    highest_priority = curdir.parent / 'bar.toml'
    with pytest.raises(FileNotFoundError):
        ConfigSource.from_config(highest_priority)
    highest_priority.touch()
    assert (ConfigSource.from_config(highest_priority)
            .path.samefile(highest_priority))
    # Also test that `True` is equivalent to the default behavior
    # (`None`), and `False` to disabling all lookup
    assert ConfigSource.from_config(True).path.samefile(high_priority)
    assert ConfigSource.from_config(False).path.samefile(default)