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