salt/tests/pytests/unit/modules/file/test_file_basics.py
Tom Doherty bd819e6ab3
Some checks failed
CI / Prepare Workflow Run (push) Has been cancelled
CI / Pre-Commit (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / NSIS Tests (push) Has been cancelled
CI / Prepare Release: (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / Build Source Tarball (push) Has been cancelled
CI / Build Salt Onedir (push) Has been cancelled
CI / Build Packages (push) Has been cancelled
CI / CI Deps (push) Has been cancelled
CI / Test Package (push) Has been cancelled
CI / Test Salt (push) Has been cancelled
CI / Combine Code Coverage (push) Has been cancelled
CI / Set the Pipeline Exit Status (push) Has been cancelled
Make file.symlink/_symlink_check() respect follow_symlinks
Fixes: #66980
2025-03-17 14:43:11 -07:00

311 lines
9 KiB
Python

import logging
import os
import shutil
import pytest
import salt.config
import salt.loader
import salt.modules.cmdmod as cmdmod
import salt.modules.config as configmod
import salt.modules.file as filemod
import salt.utils.data
import salt.utils.files
import salt.utils.platform
import salt.utils.stringutils
from tests.support.mock import MagicMock, call, patch
log = logging.getLogger(__name__)
@pytest.fixture
def configure_loader_modules():
return {
filemod: {
"__salt__": {
"config.manage_mode": configmod.manage_mode,
"cmd.run": cmdmod.run,
"cmd.run_all": cmdmod.run_all,
},
"__opts__": {
"test": False,
"file_roots": {"base": "tmp"},
"pillar_roots": {"base": "tmp"},
"cachedir": "tmp",
"grains": {},
},
"__grains__": {"kernel": "Linux"},
}
}
@pytest.fixture
def tmp_sub_dir(tmp_path):
directory = tmp_path / "file-basics-test-dir"
directory.mkdir()
yield directory
shutil.rmtree(str(directory))
@pytest.fixture
def tfile(tmp_sub_dir):
filename = str(tmp_sub_dir / "file-basics-test-file")
with salt.utils.files.fopen(filename, "w+") as fp:
fp.write("Hi hello! I am a file.")
yield filename
os.remove(filename)
@pytest.fixture
def myfile(tmp_sub_dir):
filename = str(tmp_sub_dir / "myfile")
with salt.utils.files.fopen(filename, "w+") as fp:
fp.write(salt.utils.stringutils.to_str("Hello\n"))
yield filename
os.remove(filename)
@pytest.fixture
def a_link(tmp_sub_dir):
path = tmp_sub_dir / "a_link"
linkname = str(path)
yield linkname
if path.exists():
os.remove(linkname)
@pytest.fixture
def a_hardlink(tmp_sub_dir):
path = tmp_sub_dir / "a_hardlink"
linkname = str(path)
yield linkname
if path.exists():
os.remove(linkname)
@pytest.mark.skip_on_windows(reason="os.symlink is not available on Windows")
def test_symlink_already_in_desired_state(tfile, a_link):
os.symlink(tfile, a_link)
result = filemod.symlink(tfile, a_link)
assert result
@pytest.mark.skip_on_windows(reason="os.link is not available on Windows")
def test_hardlink_sanity(tfile, a_hardlink):
target = a_hardlink
result = filemod.link(tfile, target)
assert result
@pytest.mark.skip_on_windows(reason="os.link is not available on Windows")
def test_hardlink_numlinks(tfile, a_hardlink):
target = a_hardlink
result = filemod.link(tfile, target)
name_i = os.stat(tfile).st_nlink
assert name_i > 1
@pytest.mark.skip_on_windows(reason="os.link is not available on Windows")
def test_hardlink_working(tfile, a_hardlink):
target = a_hardlink
result = filemod.link(tfile, target)
name_i = os.stat(tfile).st_ino
target_i = os.stat(target).st_ino
assert name_i == target_i
def test_source_list_for_list_returns_file_from_dict_via_http():
with patch("salt.modules.file.os.remove") as remove:
remove.return_value = None
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=[]),
"cp.list_master_dirs": MagicMock(return_value=[]),
"cp.cache_file": MagicMock(return_value="/tmp/http.conf"),
},
):
with patch("salt.utils.http.query") as http_query:
http_query.return_value = {}
ret = filemod.source_list(
[{"http://t.est.com/http/httpd.conf": "filehash"}], "", "base"
)
assert list(ret) == ["http://t.est.com/http/httpd.conf", "filehash"]
def test_source_list_use_requests():
with patch("salt.modules.file.os.remove") as remove:
remove.return_value = None
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=[]),
"cp.list_master_dirs": MagicMock(return_value=[]),
"cp.cache_file": MagicMock(return_value="/tmp/http.conf"),
},
):
expected_call = call(
"http://t.est.com/http/file1",
decode_body=False,
method="HEAD",
)
with patch(
"salt.utils.http.query", MagicMock(return_value={})
) as http_query:
ret = filemod.source_list(
[{"http://t.est.com/http/file1": "filehash"}], "", "base"
)
assert list(ret) == ["http://t.est.com/http/file1", "filehash"]
assert expected_call in http_query.mock_calls
def test_source_list_for_list_returns_existing_file():
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=["http/httpd.conf.fallback"]),
"cp.list_master_dirs": MagicMock(return_value=[]),
},
):
ret = filemod.source_list(
["salt://http/httpd.conf", "salt://http/httpd.conf.fallback"],
"filehash",
"base",
)
assert list(ret) == ["salt://http/httpd.conf.fallback", "filehash"]
def test_source_list_for_list_returns_file_from_other_env():
def list_master(env):
dct = {"base": [], "dev": ["http/httpd.conf"]}
return dct[env]
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(side_effect=list_master),
"cp.list_master_dirs": MagicMock(return_value=[]),
},
):
ret = filemod.source_list(
[
"salt://http/httpd.conf?saltenv=dev",
"salt://http/httpd.conf.fallback",
],
"filehash",
"base",
)
assert list(ret) == ["salt://http/httpd.conf?saltenv=dev", "filehash"]
def test_source_list_for_list_returns_file_from_dict():
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=["http/httpd.conf"]),
"cp.list_master_dirs": MagicMock(return_value=[]),
},
):
ret = filemod.source_list([{"salt://http/httpd.conf": ""}], "filehash", "base")
assert list(ret) == ["salt://http/httpd.conf", "filehash"]
def test_source_list_for_list_returns_existing_local_file_slash(myfile):
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=[]),
"cp.list_master_dirs": MagicMock(return_value=[]),
},
):
ret = filemod.source_list([myfile + "-foo", myfile], "filehash", "base")
assert list(ret) == [myfile, "filehash"]
def test_source_list_for_list_returns_existing_local_file_proto(myfile):
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=[]),
"cp.list_master_dirs": MagicMock(return_value=[]),
},
):
ret = filemod.source_list(
["file://" + myfile + "-foo", "file://" + myfile],
"filehash",
"base",
)
assert list(ret) == ["file://" + myfile, "filehash"]
def test_source_list_for_list_returns_local_file_slash_from_dict(myfile):
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=[]),
"cp.list_master_dirs": MagicMock(return_value=[]),
},
):
ret = filemod.source_list([{myfile: ""}], "filehash", "base")
assert list(ret) == [myfile, "filehash"]
def test_source_list_for_list_returns_local_file_proto_from_dict(myfile):
with patch.dict(
filemod.__salt__,
{
"cp.list_master": MagicMock(return_value=[]),
"cp.list_master_dirs": MagicMock(return_value=[]),
},
):
ret = filemod.source_list([{"file://" + myfile: ""}], "filehash", "base")
assert list(ret) == ["file://" + myfile, "filehash"]
def test_symlink_lexists_called_follow_symlinks_false():
tfile = "/tmp/file-basics-test-file"
a_link = "/tmp/a_link"
exists = MagicMock(return_value=False)
lexists = MagicMock(return_value=False)
with patch("os.path.exists", exists), patch("os.path.lexists", lexists), patch(
"os.symlink", MagicMock(return_value=True)
):
filemod.symlink(tfile, a_link)
lexists.assert_not_called()
exists.assert_called()
lexists.reset_mock()
exists.reset_mock()
filemod.symlink(tfile, a_link, follow_symlinks=False)
lexists.assert_called()
exists.assert_not_called()
def test_symlink_lexists_called_follow_symlinks_true():
tfile = "/tmp/file-basics-test-file"
a_link = "/tmp/a_link"
exists = MagicMock(return_value=False)
lexists = MagicMock(return_value=False)
with patch("os.path.exists", exists), patch("os.path.lexists", lexists), patch(
"os.symlink", MagicMock(return_value=True)
):
filemod.symlink(tfile, a_link, follow_symlinks=True)
lexists.assert_not_called()
exists.assert_called()