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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
|
"""Tests for the configuration routines."""
import os
import sys
from contextlib import contextmanager
from unittest.mock import call, patch
import pytest
from sqlfluff.core import FluffConfig
from sqlfluff.core.config import (
load_config_at_path,
load_config_file,
load_config_string,
load_config_up_to_path,
)
from sqlfluff.core.config.loader import (
_get_user_config_dir_path,
_load_user_appdir_config,
)
from sqlfluff.core.errors import SQLFluffUserError
config_a = {
"core": {"testing_val": "foobar", "testing_int": 4, "dialect": "mysql"},
"bar": {"foo": "barbar"},
}
@pytest.fixture
def mock_xdg_home(monkeypatch):
"""Sets the XDG_CONFIG_HOME variable."""
monkeypatch.setenv("XDG_CONFIG_HOME", "~/.config/my/special/path")
def test__config__load_file_dir():
"""Test loading config from a directory path."""
cfg = load_config_at_path(
os.path.join("test", "fixtures", "config", "inheritance_a")
)
assert cfg == config_a
def test__config__load_from_string():
"""Test loading config from a string."""
# Load a string
with open(
os.path.join("test", "fixtures", "config", "inheritance_a", ".sqlfluff")
) as f:
config_string = f.read()
cfg = load_config_string(config_string)
assert cfg == config_a
def test__config__load_file_f():
"""Test loading config from a file path."""
cfg = load_config_at_path(
os.path.join("test", "fixtures", "config", "inheritance_a", "testing.sql")
)
assert cfg == config_a
def test__config__load_file_missing_extra():
"""Test loading config from a file path if extra path is not found."""
with pytest.raises(SQLFluffUserError):
load_config_up_to_path(
os.path.join("test", "fixtures", "config", "inheritance_a", "testing.sql"),
extra_config_path="non/existent/path",
)
def test__config__load_nested():
"""Test nested overwrite and order of precedence of config files."""
cfg = load_config_up_to_path(
os.path.join(
"test", "fixtures", "config", "inheritance_a", "nested", "blah.sql"
),
extra_config_path=os.path.join(
"test",
"fixtures",
"config",
"inheritance_a",
"extra",
"this_can_have_any_name.cfg",
),
)
assert cfg == {
"core": {
# Outer .sqlfluff defines dialect & testing_val and not overridden.
"dialect": "mysql",
"testing_val": "foobar",
# tesing_int is defined in many. Inner pyproject.toml takes precedence.
"testing_int": 1,
# testing_bar is defined only in setup.cfg
"testing_bar": 7.698,
},
# bar is defined in a few, but the extra_config takes precedence.
"bar": {"foo": "foobarextra"},
# fnarr is defined in a few. Inner tox.ini takes precedence.
"fnarr": {"fnarr": {"foo": "foobar"}},
}
@contextmanager
def change_dir(path):
"""Set the current working directory to `path` for the duration of the context."""
original_dir = os.getcwd()
try:
os.chdir(path)
yield
finally:
os.chdir(original_dir)
@pytest.mark.skipif(
sys.platform == "win32",
reason="Seems test is not executed under home directory on Windows",
)
def test__config__load_parent():
"""Test that config is loaded from parent directory of current working directory."""
with change_dir(
os.path.join("test", "fixtures", "config", "inheritance_a", "nested")
):
cfg = load_config_up_to_path("blah.sql")
assert cfg == {
"core": {
"dialect": "mysql",
"testing_val": "foobar",
"testing_int": 1,
"testing_bar": 7.698,
},
"bar": {"foo": "foobar"},
"fnarr": {"fnarr": {"foo": "foobar"}},
}
def test__config__load_toml():
"""Test loading config from a pyproject.toml file."""
cfg = load_config_file(
os.path.join("test", "fixtures", "config", "toml"),
"pyproject.toml",
)
assert cfg == {
"core": {
"nocolor": True,
"verbose": 2,
"testing_int": 5,
"testing_bar": 7.698,
"testing_bool": False,
"testing_arr": ["a", "b", "c"],
"rules": ["LT03", "LT09"],
"testing_inline_table": {"x": 1},
},
"bar": {"foo": "foobar"},
"fnarr": {"fnarr": {"foo": "foobar"}},
"rules": {"capitalisation.keywords": {"capitalisation_policy": "upper"}},
}
def test__config__load_placeholder_cfg():
"""Test loading a sqlfluff configuration file for placeholder templater."""
cfg = load_config_file(
os.path.join("test", "fixtures", "config", "placeholder"),
".sqlfluff-placeholder",
)
assert cfg == {
"core": {
"testing_val": "foobar",
"testing_int": 4,
},
"bar": {"foo": "barbar"},
"templater": {
"placeholder": {
"param_style": "flyway_var",
"flyway:database": "test_db",
}
},
}
@patch("os.path.exists")
@patch("os.listdir")
@pytest.mark.skipif(sys.platform == "win32", reason="Not applicable on Windows")
@pytest.mark.parametrize(
"sys_platform,xdg_exists,default_exists,resolved_config_path,paths_checked",
[
# On linux, if the default path exists, it should be the only path we check
# and the chosen config path.
("linux", True, True, "~/.config/sqlfluff", ["~/.config/sqlfluff"]),
# On linux, if the default path doesn't exist, then (because for this
# test case we set XDG_CONFIG_HOME) it will check the default path
# but then on finding it to not exist it will then try the XDG path.
# In this case, neither actually exist and so what matters is that both
# are either checked or used - rather than one in particular being the
# end result.
(
"linux",
False,
False,
"~/.config/my/special/path/sqlfluff",
["~/.config/sqlfluff"],
),
# On MacOS, if the default config path and the XDG path don't exist, then
# we should resolve config to the default MacOS config path.
#(
# "darwin",
# False,
# False,
# "~/Library/Application Support/sqlfluff",
# ["~/.config/sqlfluff", "~/.config/my/special/path/sqlfluff"],
#),
# However, if XDG_CONFIG_HOME is set, and the path exists then that should
# be resolved _ahead of_ the default MacOS config path (as demonstrated
# by us not checking the presence of that path in the process).
# https://github.com/sqlfluff/sqlfluff/issues/889
(
"darwin",
True,
False,
"~/.config/my/special/path/sqlfluff",
["~/.config/sqlfluff", "~/.config/my/special/path/sqlfluff"],
),
],
)
def test__config__get_user_config_dir_path(
mock_listdir,
mock_path_exists,
mock_xdg_home,
sys_platform,
xdg_exists,
default_exists,
resolved_config_path,
paths_checked,
):
"""Test loading config from user appdir."""
xdg_home = os.environ.get("XDG_CONFIG_HOME")
assert xdg_home, "XDG HOME should be set by the mock. Something has gone wrong."
xdg_config_path = xdg_home + "/sqlfluff"
def path_exists(check_path):
"""Patch for os.path.exists which depends on test parameters.
Returns:
True, unless `default_exists` is `False` and the path passed to
the function is the default config path, or unless `xdg_exists`
is `False` and the path passed is the XDG config path.
"""
resolved_path = os.path.expanduser(check_path)
if (
resolved_path == os.path.expanduser("~/.config/sqlfluff")
and not default_exists
):
return False
if resolved_path == os.path.expanduser(xdg_config_path) and not xdg_exists:
return False
return True
mock_path_exists.side_effect = path_exists
# Get the config path as though we are on macOS.
resolved_path = _get_user_config_dir_path(sys_platform)
assert os.path.expanduser(resolved_path) == os.path.expanduser(resolved_config_path)
mock_path_exists.assert_has_calls(
[call(os.path.expanduser(path)) for path in paths_checked]
)
@patch("os.path.exists")
@patch("sqlfluff.core.config.loader.load_config_at_path")
def test__config__load_user_appdir_config(mock_load_config, mock_path_exists):
"""Test _load_user_appdir_config.
NOTE: We mock `load_config_at_path()` so we can be really focussed with this test
and also not need to actually interact with local home directories.
"""
mock_load_config.side_effect = lambda x: {}
mock_path_exists.side_effect = lambda x: True
_load_user_appdir_config()
# It will check that the default config path exists...
mock_path_exists.assert_has_calls([call(os.path.expanduser("~/.config/sqlfluff"))])
# ...and assuming it does, it will try and load config files at that path.
mock_load_config.assert_has_calls([call(os.path.expanduser("~/.config/sqlfluff"))])
def test__config__toml_list_config():
"""Test Parsing TOML list of values."""
loaded_config = load_config_file(
os.path.join("test", "fixtures", "config", "toml"),
"pyproject.toml",
)
loaded_config["core"]["dialect"] = "ansi"
cfg = FluffConfig(loaded_config)
# Verify we can later retrieve the config values.
assert cfg.get("dialect") == "ansi"
assert cfg.get("rules") == ["LT03", "LT09"]
|