mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Add some funtional tests
Add functional tests for the following: - file.readlink - file.replace - file.symlink Remove unit tests for file.replace as they are duplicated in the added functional test
This commit is contained in:
parent
6680407756
commit
a35b29b265
9 changed files with 645 additions and 291 deletions
|
@ -3707,10 +3707,21 @@ def is_link(path):
|
|||
return os.path.islink(os.path.expanduser(path))
|
||||
|
||||
|
||||
def symlink(src, path):
|
||||
def symlink(src, path, force=False):
|
||||
"""
|
||||
Create a symbolic link (symlink, soft link) to a file
|
||||
|
||||
Args:
|
||||
|
||||
src (str): The path to a file or directory
|
||||
|
||||
link (str): The path to the link. Must be an absolute path
|
||||
|
||||
force (bool): Overwrite an existing symlink with the same name
|
||||
|
||||
Returns:
|
||||
bool: True if successful, otherwise False
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -3719,22 +3730,37 @@ def symlink(src, path):
|
|||
"""
|
||||
path = os.path.expanduser(path)
|
||||
|
||||
try:
|
||||
if os.path.normpath(salt.utils.path.readlink(path)) == os.path.normpath(src):
|
||||
log.debug("link already in correct state: %s -> %s", path, src)
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
if os.path.islink(path):
|
||||
try:
|
||||
if os.path.normpath(salt.utils.path.readlink(path)) == os.path.normpath(
|
||||
src
|
||||
):
|
||||
log.debug("link already in correct state: %s -> %s", path, src)
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if force:
|
||||
os.unlink(path)
|
||||
else:
|
||||
msg = "Found existing symlink: {}".format(path)
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
if os.path.exists(link):
|
||||
msg = "Existing path is not a symlink: {}".format(path)
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
if not os.path.exists(src):
|
||||
raise CommandExecutionError("Source path does not exist: {}".format(src))
|
||||
|
||||
if not os.path.isabs(path):
|
||||
raise SaltInvocationError("File path must be absolute.")
|
||||
raise SaltInvocationError("Link path must be absolute: {}".format(path))
|
||||
|
||||
try:
|
||||
os.symlink(src, path)
|
||||
return True
|
||||
except OSError:
|
||||
raise CommandExecutionError("Could not create '{}'".format(path))
|
||||
return False
|
||||
|
||||
|
||||
def rename(src, dst):
|
||||
|
@ -3928,7 +3954,27 @@ def readlink(path, canonicalize=False):
|
|||
.. versionadded:: 2014.1.0
|
||||
|
||||
Return the path that a symlink points to
|
||||
If canonicalize is set to True, then it return the final target
|
||||
|
||||
Args:
|
||||
|
||||
path (str):
|
||||
The path to the symlink
|
||||
|
||||
canonicalize (bool):
|
||||
Get the canonical path eliminating any symbolic links encountered in
|
||||
the path
|
||||
|
||||
Returns:
|
||||
|
||||
str: The path that the symlink points to
|
||||
|
||||
Raises:
|
||||
|
||||
SaltInvocationError: path is not absolute
|
||||
|
||||
SaltInvocationError: path is not a link
|
||||
|
||||
CommandExecutionError: error reading the symbolic link
|
||||
|
||||
CLI Example:
|
||||
|
||||
|
@ -3937,17 +3983,25 @@ def readlink(path, canonicalize=False):
|
|||
salt '*' file.readlink /path/to/link
|
||||
"""
|
||||
path = os.path.expanduser(path)
|
||||
path = os.path.expandvars(path)
|
||||
|
||||
if not os.path.isabs(path):
|
||||
raise SaltInvocationError("Path to link must be absolute.")
|
||||
raise SaltInvocationError("Path to link must be absolute: {}".format(path))
|
||||
|
||||
if not os.path.islink(path):
|
||||
raise SaltInvocationError("A valid link was not specified.")
|
||||
raise SaltInvocationError("A valid link was not specified: {}".format(path))
|
||||
|
||||
if canonicalize:
|
||||
return os.path.realpath(path)
|
||||
else:
|
||||
return salt.utils.path.readlink(path)
|
||||
try:
|
||||
return salt.utils.path.readlink(path)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EINVAL:
|
||||
raise CommandExecutionError("Not a symbolic link: {}".format(path))
|
||||
raise CommandExecutionError(exc.__str__())
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
raise CommandExecutionError(exc)
|
||||
|
||||
|
||||
def readdir(path):
|
||||
|
|
|
@ -100,6 +100,7 @@ from salt.modules.file import (
|
|||
psed,
|
||||
read,
|
||||
readdir,
|
||||
readlink,
|
||||
rename,
|
||||
replace,
|
||||
restore_backup,
|
||||
|
@ -159,7 +160,7 @@ def __virtual__():
|
|||
global get_diff, line, _get_flags, extract_hash, comment_line
|
||||
global access, copy, readdir, read, rmdir, truncate, replace, search
|
||||
global _binary_replace, _get_bkroot, list_backups, restore_backup
|
||||
global _splitlines_preserving_trailing_newline
|
||||
global _splitlines_preserving_trailing_newline, readlink
|
||||
global blockreplace, prepend, seek_read, seek_write, rename, lstat
|
||||
global write, pardir, join, _add_flags, apply_template_on_contents
|
||||
global path_exists_glob, comment, uncomment, _mkstemp_copy
|
||||
|
@ -208,6 +209,7 @@ def __virtual__():
|
|||
access = _namespaced_function(access, globals())
|
||||
copy = _namespaced_function(copy, globals())
|
||||
readdir = _namespaced_function(readdir, globals())
|
||||
readlink = _namespaced_function(readlink, globals())
|
||||
read = _namespaced_function(read, globals())
|
||||
rmdir = _namespaced_function(rmdir, globals())
|
||||
truncate = _namespaced_function(truncate, globals())
|
||||
|
@ -1184,7 +1186,7 @@ def remove(path, force=False):
|
|||
return True
|
||||
|
||||
|
||||
def symlink(src, link):
|
||||
def symlink(src, link, force=False):
|
||||
"""
|
||||
Create a symbolic link to a file
|
||||
|
||||
|
@ -1196,10 +1198,17 @@ def symlink(src, link):
|
|||
If it doesn't, an error will be raised.
|
||||
|
||||
Args:
|
||||
|
||||
src (str): The path to a file or directory
|
||||
link (str): The path to the link
|
||||
|
||||
link (str): The path to the link. Must be an absolute path
|
||||
|
||||
force (bool):
|
||||
Overwrite an existing symlink with the same name
|
||||
.. versionadded:: 3005
|
||||
|
||||
Returns:
|
||||
|
||||
bool: True if successful, otherwise False
|
||||
|
||||
CLI Example:
|
||||
|
@ -1215,11 +1224,31 @@ def symlink(src, link):
|
|||
"Symlinks are only supported on Windows Vista or later."
|
||||
)
|
||||
|
||||
if not os.path.exists(src):
|
||||
raise SaltInvocationError("The given source path does not exist.")
|
||||
if os.path.islink(link):
|
||||
try:
|
||||
if os.path.normpath(salt.utils.path.readlink(link)) == os.path.normpath(
|
||||
src
|
||||
):
|
||||
log.debug("link already in correct state: %s -> %s", link, src)
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if not os.path.isabs(src):
|
||||
raise SaltInvocationError("File path must be absolute.")
|
||||
if force:
|
||||
os.unlink(link)
|
||||
else:
|
||||
msg = "Found existing symlink: {}".format(link)
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
if os.path.exists(link):
|
||||
msg = "Existing path is not a symlink: {}".format(link)
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
if not os.path.exists(src):
|
||||
raise CommandExecutionError("Source path does not exist: {}".format(src))
|
||||
|
||||
if not os.path.isabs(link):
|
||||
raise SaltInvocationError("Link path must be absolute: {}".format(link))
|
||||
|
||||
# ensure paths are using the right slashes
|
||||
src = os.path.normpath(src)
|
||||
|
@ -1274,43 +1303,6 @@ def is_link(path):
|
|||
raise CommandExecutionError(exc)
|
||||
|
||||
|
||||
def readlink(path):
|
||||
"""
|
||||
Return the path that a symlink points to
|
||||
|
||||
This is only supported on Windows Vista or later.
|
||||
|
||||
Inline with Unix behavior, this function will raise an error if the path is
|
||||
not a symlink, however, the error raised will be a SaltInvocationError, not
|
||||
an OSError.
|
||||
|
||||
Args:
|
||||
path (str): The path to the symlink
|
||||
|
||||
Returns:
|
||||
str: The path that the symlink points to
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' file.readlink /path/to/link
|
||||
"""
|
||||
if sys.getwindowsversion().major < 6:
|
||||
raise SaltInvocationError(
|
||||
"Symlinks are only supported on Windows Vista or later."
|
||||
)
|
||||
|
||||
try:
|
||||
return __utils__["path.readlink"](path)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EINVAL:
|
||||
raise CommandExecutionError("{} is not a symbolic link".format(path))
|
||||
raise CommandExecutionError(exc.__str__())
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
raise CommandExecutionError(exc)
|
||||
|
||||
|
||||
def mkdir(
|
||||
path, owner=None, grant_perms=None, deny_perms=None, inheritance=True, reset=False
|
||||
):
|
||||
|
|
|
@ -7308,7 +7308,9 @@ def rename(name, source, force=False, makedirs=False, **kwargs):
|
|||
|
||||
"""
|
||||
name = os.path.expanduser(name)
|
||||
name = os.path.expandvars(name)
|
||||
source = os.path.expanduser(source)
|
||||
source = os.path.expandvars(source)
|
||||
|
||||
ret = {"name": name, "changes": {}, "comment": "", "result": True}
|
||||
if not name:
|
||||
|
|
|
@ -43,6 +43,7 @@ salt/modules/augeas_cfg.py:
|
|||
- pytests.unit.states.test_augeas
|
||||
|
||||
salt/modules/cp.py:
|
||||
- pytests.functional.modules.file.test_replace
|
||||
- pytests.unit.modules.file.test_file_basics
|
||||
- pytests.unit.modules.file.test_file_block_replace
|
||||
- pytests.unit.modules.file.test_file_chattr
|
||||
|
@ -51,7 +52,6 @@ salt/modules/cp.py:
|
|||
- pytests.unit.modules.file.test_file_line
|
||||
- pytests.unit.modules.file.test_file_lsattr
|
||||
- pytests.unit.modules.file.test_file_module
|
||||
- pytests.unit.modules.file.test_file_replace
|
||||
- pytests.unit.modules.file.test_file_selinux
|
||||
- pytests.unit.states.file.test_copy
|
||||
- integration.modules.test_file
|
||||
|
|
92
tests/pytests/functional/modules/file/test_readlink.py
Normal file
92
tests/pytests/functional/modules/file/test_readlink.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Tests for file.readlink function
|
||||
"""
|
||||
# nox -e pytest-zeromq-3.8(coverage=False) -- -vvv --run-slow --run-destructive tests\pytests\functional\modules\file\test_readlink.py
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import salt.utils.path
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def file(modules):
|
||||
return modules.file
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def source():
|
||||
with pytest.helpers.temp_file(contents="Source content") as source:
|
||||
yield source
|
||||
|
||||
|
||||
def test_readlink(file, source):
|
||||
"""
|
||||
Test readlink with defaults
|
||||
"""
|
||||
target = source.parent / "symlink.lnk"
|
||||
target.symlink_to(source)
|
||||
try:
|
||||
result = file.readlink(path=target)
|
||||
assert result == str(source)
|
||||
finally:
|
||||
target.unlink()
|
||||
|
||||
|
||||
def test_readlink_relative_path(file):
|
||||
"""
|
||||
Test readlink with relative path
|
||||
Should throw a SaltInvocationError
|
||||
"""
|
||||
with pytest.raises(SaltInvocationError) as exc:
|
||||
file.readlink(path="..\\test")
|
||||
assert "Path to link must be absolute" in exc.value.message
|
||||
|
||||
|
||||
def test_readlink_not_a_link(file, source):
|
||||
"""
|
||||
Test readlink where the path is not a link
|
||||
Should throw a SaltInvocationError
|
||||
"""
|
||||
with pytest.raises(SaltInvocationError) as exc:
|
||||
file.readlink(path=source)
|
||||
assert "A valid link was not specified" in exc.value.message
|
||||
|
||||
|
||||
def test_readlink_non_canonical(file, source):
|
||||
"""
|
||||
Test readlink where there are nested symlinks and canonicalize=False
|
||||
Should resolve to the first symlink
|
||||
"""
|
||||
intermediate = source.parent / "intermediate.lnk"
|
||||
intermediate.symlink_to(source)
|
||||
target = source.parent / "symlink.lnk"
|
||||
target.symlink_to(intermediate)
|
||||
try:
|
||||
result = file.readlink(path=target)
|
||||
assert result == str(intermediate)
|
||||
finally:
|
||||
intermediate.unlink()
|
||||
target.unlink()
|
||||
|
||||
|
||||
def test_readlink_canonical(file, source):
|
||||
"""
|
||||
Test readlink where there are nested symlinks and canonicalize=True
|
||||
Should resolve all nested symlinks returning the path to the source file
|
||||
"""
|
||||
intermediate = source.parent / "intermediate.lnk"
|
||||
intermediate.symlink_to(source)
|
||||
target = source.parent / "symlink.lnk"
|
||||
target.symlink_to(intermediate)
|
||||
try:
|
||||
result = file.readlink(path=target, canonicalize=True)
|
||||
assert result == str(source)
|
||||
finally:
|
||||
intermediate.unlink()
|
||||
target.unlink()
|
199
tests/pytests/functional/modules/file/test_replace.py
Normal file
199
tests/pytests/functional/modules/file/test_replace.py
Normal file
|
@ -0,0 +1,199 @@
|
|||
"""
|
||||
Tests for file.rename function
|
||||
"""
|
||||
# nox -e pytest-zeromq-3.8(coverage=False) -- -vvv --run-slow --run-destructive tests\pytests\functional\modules\file\test_replace.py
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def file(modules):
|
||||
return modules.file
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def multiline_string():
|
||||
return """\
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rhoncus
|
||||
enim ac bibendum vulputate. Etiam nibh velit, placerat ac auctor in,
|
||||
lacinia a turpis. Nulla elit elit, ornare in sodales eu, aliquam sit
|
||||
amet nisl.
|
||||
|
||||
Fusce ac vehicula lectus. Vivamus justo nunc, pulvinar in ornare nec,
|
||||
sollicitudin id sem. Pellentesque sed ipsum dapibus, dapibus elit id,
|
||||
malesuada nisi.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
|
||||
venenatis tellus eget massa facilisis, in auctor ante aliquet. Sed nec
|
||||
cursus metus. Curabitur massa urna, vehicula id porttitor sed, lobortis
|
||||
quis leo.
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def multiline_file(tmp_path_factory, multiline_string):
|
||||
temp_dir = tmp_path_factory.mktemp("replace-tests")
|
||||
test_file = temp_dir / "multiline-file.txt"
|
||||
test_file.write_text(multiline_string)
|
||||
yield test_file
|
||||
shutil.rmtree(str(temp_dir))
|
||||
|
||||
|
||||
def test_no_backup(file, multiline_file):
|
||||
# Backup file should NOT be created
|
||||
bak_file = f"{multiline_file}.bak"
|
||||
assert "Salticus" not in multiline_file.read_text()
|
||||
file.replace(multiline_file, "Etiam", "Salticus", backup=False)
|
||||
assert "Salticus" in multiline_file.read_text()
|
||||
assert not os.path.exists(bak_file)
|
||||
|
||||
|
||||
def test_backup(file, multiline_file):
|
||||
# Should create a backup file. This is basically the default
|
||||
bak_file = f"{multiline_file}.bak"
|
||||
file.replace(multiline_file, "Etiam", "Salticus")
|
||||
assert "Salticus" in multiline_file.read_text()
|
||||
assert os.path.exists(bak_file)
|
||||
|
||||
|
||||
def test_append_if_not_found_no_match_newline(file):
|
||||
contents = "foo=1\nbar=2\n"
|
||||
expected = "foo=1\nbar=2\nbaz=\\g<value>\n"
|
||||
with pytest.helpers.temp_file("test_file.txt", contents) as target:
|
||||
file.replace(
|
||||
path=target,
|
||||
pattern="#*baz=(?P<value>.*)",
|
||||
repl="baz=\\g<value>",
|
||||
append_if_not_found=True,
|
||||
)
|
||||
assert target.read_text() == expected
|
||||
|
||||
|
||||
def test_append_if_not_found_no_match_no_newline(file):
|
||||
contents = "foo=1\nbar=2"
|
||||
expected = "foo=1\nbar=2\nbaz=\\g<value>\n"
|
||||
with pytest.helpers.temp_file("test_file.txt", contents) as target:
|
||||
file.replace(
|
||||
path=target,
|
||||
pattern="#*baz=(?P<value>.*)",
|
||||
repl="baz=\\g<value>",
|
||||
append_if_not_found=True,
|
||||
)
|
||||
assert target.read_text() == expected
|
||||
|
||||
|
||||
def test_append_if_not_found_empty_file(file):
|
||||
# A newline should NOT be added in empty files
|
||||
contents = None
|
||||
expected = "baz=\\g<value>\n"
|
||||
with pytest.helpers.temp_file("test_file.txt", contents) as target:
|
||||
file.replace(
|
||||
path=target,
|
||||
pattern="#*baz=(?P<value>.*)",
|
||||
repl="baz=\\g<value>",
|
||||
append_if_not_found=True,
|
||||
)
|
||||
assert target.read_text() == expected
|
||||
|
||||
|
||||
def test_append_if_not_found_content(file):
|
||||
# Using not_found_content, rather than repl
|
||||
contents = None
|
||||
expected = "baz=3\n"
|
||||
with pytest.helpers.temp_file("test_file.txt", contents) as target:
|
||||
file.replace(
|
||||
path=target,
|
||||
pattern="#*baz=(?P<value>.*)",
|
||||
repl="baz=\\g<value>",
|
||||
append_if_not_found=True,
|
||||
not_found_content="baz=3",
|
||||
)
|
||||
assert target.read_text() == expected
|
||||
|
||||
|
||||
def test_append_if_not_found_no_append_on_match(file):
|
||||
# Not appending if matches
|
||||
contents = "foo=1\nbaz=42\nbar=2"
|
||||
with pytest.helpers.temp_file("test_file.txt", contents) as target:
|
||||
file.replace(
|
||||
path=target,
|
||||
pattern="#*baz=(?P<value>.*)",
|
||||
repl="baz=\\g<value>",
|
||||
append_if_not_found=True,
|
||||
not_found_content="baz=3",
|
||||
)
|
||||
assert target.read_text() == contents
|
||||
|
||||
|
||||
def test_dry_run(file, multiline_file):
|
||||
before_time = os.stat(multiline_file).st_mtime
|
||||
file.replace(multiline_file, r"Etiam", "Salticus", dry_run=True)
|
||||
after_time = os.stat(multiline_file).st_mtime
|
||||
assert before_time == after_time
|
||||
|
||||
|
||||
def test_show_changes(file, multiline_file):
|
||||
ret = file.replace(multiline_file, r"Etiam", "Salticus", show_changes=True)
|
||||
assert ret.startswith("---") # looks like a diff
|
||||
|
||||
|
||||
def test_no_show_changes(file, multiline_file):
|
||||
ret = file.replace(multiline_file, r"Etiam", "Salticus", show_changes=False)
|
||||
assert isinstance(ret, bool)
|
||||
|
||||
|
||||
def test_re_str_flags(file, multiline_file):
|
||||
file.replace(
|
||||
multiline_file, r"etiam", "Salticus", flags=["MULTILINE", "ignorecase"]
|
||||
)
|
||||
assert "Salticus" in multiline_file.read_text()
|
||||
|
||||
|
||||
def test_re_int_flags(file, multiline_file):
|
||||
# flag for multiline and ignore case is 10
|
||||
file.replace(multiline_file, r"etiam", "Salticus", flags=10)
|
||||
assert "Salticus" in multiline_file.read_text()
|
||||
|
||||
|
||||
def test_numeric_repl(file, multiline_file):
|
||||
"""
|
||||
This test covers cases where the replacement string is numeric. The CLI
|
||||
parser yaml-fies it into a numeric type. If not converted back to a string
|
||||
type in file.replace, a TypeError occurs when the replace is attempted. See
|
||||
https://github.com/saltstack/salt/issues/9097 for more information.
|
||||
"""
|
||||
file.replace(multiline_file, r"Etiam", 123)
|
||||
assert "123" in multiline_file.read_text()
|
||||
|
||||
|
||||
def test_search_only_return_true(file, multiline_file):
|
||||
ret = file.replace(multiline_file, r"Etiam", "Salticus", search_only=True)
|
||||
assert isinstance(ret, bool)
|
||||
assert ret is True
|
||||
|
||||
|
||||
def test_search_only_return_false(file, multiline_file):
|
||||
ret = file.replace(multiline_file, r"Etian", "Salticus", search_only=True)
|
||||
assert isinstance(ret, bool)
|
||||
assert ret is False
|
||||
|
||||
|
||||
def test_symlink(file, multiline_file):
|
||||
# https://github.com/saltstack/salt/pull/61326
|
||||
try:
|
||||
# Create a symlink to target
|
||||
sym_link = multiline_file.parent / "symlink.lnk"
|
||||
sym_link.symlink_to(multiline_file)
|
||||
# file.replace on the symlink
|
||||
file.replace(sym_link, r"Etiam", "Salticus")
|
||||
# test that the target was changed
|
||||
assert "Salticus" in multiline_file.read_text()
|
||||
finally:
|
||||
if os.path.exists(sym_link):
|
||||
sym_link.unlink()
|
121
tests/pytests/functional/modules/file/test_symlink.py
Normal file
121
tests/pytests/functional/modules/file/test_symlink.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
"""
|
||||
Tests for file.symlink function
|
||||
"""
|
||||
# nox -e pytest-zeromq-3.8(coverage=False) -- -vvv --run-slow --run-destructive tests\pytests\functional\modules\file\test_symlink.py
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import salt.utils.path
|
||||
from salt.exceptions import CommandExecutionError, SaltInvocationError
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def file(modules):
|
||||
return modules.file
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def source():
|
||||
with pytest.helpers.temp_file(contents="Source content") as source:
|
||||
yield source
|
||||
|
||||
|
||||
def test_symlink(file, source):
|
||||
"""
|
||||
Test symlink with defaults
|
||||
"""
|
||||
target = source.parent / "symlink.lnk"
|
||||
try:
|
||||
file.symlink(source, target)
|
||||
assert salt.utils.path.islink(target)
|
||||
finally:
|
||||
target.unlink()
|
||||
|
||||
|
||||
def test_symlink_exists_same(file, source):
|
||||
"""
|
||||
Test symlink with an existing symlink to the correct file
|
||||
Timestamps should not change
|
||||
"""
|
||||
target = source.parent / "symlink.lnk"
|
||||
target.symlink_to(source)
|
||||
try:
|
||||
before_time = os.stat(target).st_mtime
|
||||
ret = file.symlink(source, target)
|
||||
after_time = os.stat(target).st_mtime
|
||||
assert before_time == after_time
|
||||
assert ret is True
|
||||
finally:
|
||||
target.unlink()
|
||||
|
||||
|
||||
def test_symlink_exists_different(file, source):
|
||||
"""
|
||||
Test symlink with an existing symlink to a different file
|
||||
Should throw a CommandExecutionError
|
||||
"""
|
||||
dif_source = source.parent / "dif_source.txt"
|
||||
target = source.parent / "symlink.lnk"
|
||||
target.symlink_to(dif_source)
|
||||
try:
|
||||
with pytest.raises(CommandExecutionError) as exc:
|
||||
file.symlink(source, target)
|
||||
assert "Found existing symlink:" in exc.value.message
|
||||
finally:
|
||||
target.unlink()
|
||||
|
||||
|
||||
def test_symlink_exists_file(file, source):
|
||||
"""
|
||||
Test symlink when the existing file is not a link
|
||||
We don't do anything because we do not want to destroy any data
|
||||
Should throw a CommandExecutionError
|
||||
"""
|
||||
with pytest.helpers.temp_file("symlink.txt", contents="Source content") as target:
|
||||
with pytest.raises(CommandExecutionError) as exc:
|
||||
file.symlink(source, target)
|
||||
assert "Existing path is not a symlink:" in exc.value.message
|
||||
|
||||
|
||||
def test_symlink_exists_different_force(file, source):
|
||||
"""
|
||||
Test symlink with an existing symlink to a different file with force=True
|
||||
Should destroy the existing symlink and generate a new one to the correct
|
||||
location
|
||||
"""
|
||||
dif_source = source.parent / "dif_source.txt"
|
||||
target = source.parent / "symlink.lnk"
|
||||
target.symlink_to(dif_source)
|
||||
try:
|
||||
file.symlink(source, target, force=True)
|
||||
assert salt.utils.path.readlink(target) == str(source)
|
||||
finally:
|
||||
target.unlink()
|
||||
|
||||
|
||||
def test_symlink_source_not_exist(file, source):
|
||||
"""
|
||||
Test symlink when the source file does not exist
|
||||
Should throw a CommandExecutionError
|
||||
"""
|
||||
target = source.parent / "symlink.lnk"
|
||||
fake_source = source.parent / "fake_source.txt"
|
||||
with pytest.raises(CommandExecutionError) as exc:
|
||||
file.symlink(fake_source, target)
|
||||
assert "Source path does not exist" in exc.value.message
|
||||
|
||||
|
||||
def test_symlink_target_relative_path(file, source):
|
||||
"""
|
||||
Test symlink when the target file is a relative path
|
||||
Should throw a SaltInvocationError
|
||||
"""
|
||||
target = "..{}symlink.lnk".format(os.path.sep)
|
||||
with pytest.raises(SaltInvocationError) as exc:
|
||||
file.symlink(source, target)
|
||||
assert "Link path must be absolute" in exc.value.message
|
127
tests/pytests/functional/states/file/test_rename.py
Normal file
127
tests/pytests/functional/states/file/test_rename.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
"""
|
||||
Tests for file.rename state function
|
||||
"""
|
||||
# nox -e pytest-zeromq-3.8(coverage=False) -- -vvv --run-slow --run-destructive tests\pytests\functional\states\file\test_rename.py
|
||||
|
||||
import pytest
|
||||
import salt.utils.path
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def file(states):
|
||||
return states.file
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def source():
|
||||
with pytest.helpers.temp_file(
|
||||
name="old_name.txt", contents="Source content"
|
||||
) as source:
|
||||
yield source
|
||||
|
||||
|
||||
def test_defaults(file, source):
|
||||
"""
|
||||
Test file.rename with defaults
|
||||
"""
|
||||
new_name = source.parent / "new_name.txt"
|
||||
try:
|
||||
file.rename(name=str(new_name), source=str(source))
|
||||
assert new_name.exists()
|
||||
assert not source.exists()
|
||||
finally:
|
||||
new_name.unlink()
|
||||
|
||||
|
||||
def test_relative_name(file):
|
||||
"""
|
||||
Test file.rename when name is a relative path
|
||||
"""
|
||||
result = file.rename(name="..\\rel\\path\\test", source=str(source))
|
||||
assert "is not an absolute path" in result.filtered["comment"]
|
||||
assert result.filtered["result"] is False
|
||||
|
||||
|
||||
def test_missing_source(file, source):
|
||||
"""
|
||||
Test file.rename with the source file is missing
|
||||
"""
|
||||
new_name = source.parent / "new_name.txt"
|
||||
missing_name = source.parent / "missing.txt"
|
||||
result = file.rename(name=str(new_name), source=str(missing_name))
|
||||
assert "has already been moved out of place" in result.filtered["comment"]
|
||||
assert result.filtered["result"] is True
|
||||
|
||||
|
||||
def test_target_exists(file, source):
|
||||
"""
|
||||
Test file.rename when there is an existing file with the new name
|
||||
"""
|
||||
new_name = source.parent / "new_name.txt"
|
||||
new_name.write_text("existing file")
|
||||
try:
|
||||
result = file.rename(name=str(new_name), source=str(source))
|
||||
assert "exists and will not be overwritten" in result.filtered["comment"]
|
||||
assert result.filtered["result"] is True
|
||||
finally:
|
||||
new_name.unlink()
|
||||
|
||||
|
||||
def test_target_exists_force(file, source):
|
||||
"""
|
||||
Test file.rename when there is an existing file with the new name and
|
||||
force=True
|
||||
"""
|
||||
new_name = source.parent / "new_name.txt"
|
||||
new_name.write_text("existing file")
|
||||
try:
|
||||
file.rename(name=str(new_name), source=str(source), force=True)
|
||||
assert new_name.exists()
|
||||
assert not source.exists()
|
||||
assert new_name.read_text() == "Source content"
|
||||
finally:
|
||||
new_name.unlink()
|
||||
|
||||
|
||||
def test_test_is_true(file, source):
|
||||
new_name = source.parent / "new_name.txt"
|
||||
result = file.rename(name=str(new_name), source=str(source), test=True)
|
||||
assert "is set to be moved to" in result.filtered["comment"]
|
||||
assert result.filtered["result"] is None
|
||||
|
||||
|
||||
def test_missing_dirs(file, source):
|
||||
new_name = source.parent / "missing_subdir" / "new_name.txt"
|
||||
result = file.rename(name=str(new_name), source=str(source))
|
||||
assert "is not present" in result.filtered["comment"]
|
||||
assert result.filtered["result"] is False
|
||||
|
||||
|
||||
def test_missing_dirs_makedirs(file, source):
|
||||
new_name = source.parent / "missing_subdir" / "new_name.txt"
|
||||
try:
|
||||
file.rename(name=str(new_name), source=str(source), makedirs=True)
|
||||
assert new_name.exists()
|
||||
assert not source.exists()
|
||||
finally:
|
||||
new_name.unlink()
|
||||
new_name.parent.rmdir()
|
||||
|
||||
|
||||
def test_source_is_link(file, source):
|
||||
link_source = source.parent / "link_source.lnk"
|
||||
link_source.symlink_to(source)
|
||||
new_name = source.parent / "new_name.lnk"
|
||||
try:
|
||||
file.rename(name=str(new_name), source=str(link_source))
|
||||
assert new_name.exists()
|
||||
assert new_name.is_symlink()
|
||||
assert salt.utils.path.readlink(new_name) == str(source)
|
||||
assert new_name.read_text() == "Source content"
|
||||
assert not link_source.exists()
|
||||
finally:
|
||||
new_name.unlink()
|
|
@ -1,233 +0,0 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import textwrap
|
||||
|
||||
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
|
||||
|
||||
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"},
|
||||
"__utils__": {
|
||||
"files.is_text": MagicMock(return_value=True),
|
||||
"stringutils.get_diff": salt.utils.stringutils.get_diff,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multiline_string():
|
||||
multiline_string = textwrap.dedent(
|
||||
"""\
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rhoncus
|
||||
enim ac bibendum vulputate. Etiam nibh velit, placerat ac auctor in,
|
||||
lacinia a turpis. Nulla elit elit, ornare in sodales eu, aliquam sit
|
||||
amet nisl.
|
||||
|
||||
Fusce ac vehicula lectus. Vivamus justo nunc, pulvinar in ornare nec,
|
||||
sollicitudin id sem. Pellentesque sed ipsum dapibus, dapibus elit id,
|
||||
malesuada nisi.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
|
||||
venenatis tellus eget massa facilisis, in auctor ante aliquet. Sed nec
|
||||
cursus metus. Curabitur massa urna, vehicula id porttitor sed, lobortis
|
||||
quis leo.
|
||||
"""
|
||||
)
|
||||
|
||||
return multiline_string
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multiline_file(tmp_path, multiline_string):
|
||||
multiline_file = str(tmp_path / "multiline-file.txt")
|
||||
|
||||
with salt.utils.files.fopen(multiline_file, "w+") as file_handle:
|
||||
file_handle.write(multiline_string)
|
||||
|
||||
yield multiline_file
|
||||
shutil.rmtree(str(tmp_path))
|
||||
|
||||
|
||||
# Make a unique subdir to avoid any tempfile conflicts
|
||||
@pytest.fixture
|
||||
def subdir(tmp_path):
|
||||
subdir = tmp_path / "test-file-replace-subdir"
|
||||
subdir.mkdir()
|
||||
yield subdir
|
||||
shutil.rmtree(str(subdir))
|
||||
|
||||
|
||||
def test_replace(multiline_file):
|
||||
filemod.replace(multiline_file, r"Etiam", "Salticus", backup=False)
|
||||
|
||||
with salt.utils.files.fopen(multiline_file, "r") as fp:
|
||||
assert "Salticus" in salt.utils.stringutils.to_unicode(fp.read())
|
||||
|
||||
|
||||
def test_replace_append_if_not_found(subdir):
|
||||
"""
|
||||
Check that file.replace append_if_not_found works
|
||||
"""
|
||||
args = {
|
||||
"pattern": "#*baz=(?P<value>.*)",
|
||||
"repl": "baz=\\g<value>",
|
||||
"append_if_not_found": True,
|
||||
}
|
||||
base = os.linesep.join(["foo=1", "bar=2"])
|
||||
|
||||
# File ending with a newline, no match
|
||||
with salt.utils.files.fopen(str(subdir / "tfile"), "w+b") as tfile:
|
||||
tfile.write(salt.utils.stringutils.to_bytes(base + os.linesep))
|
||||
tfile.flush()
|
||||
filemod.replace(tfile.name, **args)
|
||||
expected = os.linesep.join([base, "baz=\\g<value>"]) + os.linesep
|
||||
with salt.utils.files.fopen(tfile.name) as tfile2:
|
||||
assert salt.utils.stringutils.to_unicode(tfile2.read()) == expected
|
||||
os.remove(tfile.name)
|
||||
|
||||
# File not ending with a newline, no match
|
||||
with salt.utils.files.fopen(str(subdir / "tfile"), "w+b") as tfile:
|
||||
tfile.write(salt.utils.stringutils.to_bytes(base))
|
||||
tfile.flush()
|
||||
filemod.replace(tfile.name, **args)
|
||||
with salt.utils.files.fopen(tfile.name) as tfile2:
|
||||
assert salt.utils.stringutils.to_unicode(tfile2.read()) == expected
|
||||
os.remove(tfile.name)
|
||||
|
||||
# A newline should not be added in empty files
|
||||
with salt.utils.files.fopen(str(subdir / "tfile"), "w+b") as tfile:
|
||||
pass
|
||||
filemod.replace(tfile.name, **args)
|
||||
expected = args["repl"] + os.linesep
|
||||
with salt.utils.files.fopen(tfile.name) as tfile2:
|
||||
assert salt.utils.stringutils.to_unicode(tfile2.read()) == expected
|
||||
os.remove(tfile.name)
|
||||
|
||||
# Using not_found_content, rather than repl
|
||||
with salt.utils.files.fopen(str(subdir / "tfile"), "w+b") as tfile:
|
||||
tfile.write(salt.utils.stringutils.to_bytes(base))
|
||||
tfile.flush()
|
||||
args["not_found_content"] = "baz=3"
|
||||
expected = os.linesep.join([base, "baz=3"]) + os.linesep
|
||||
filemod.replace(tfile.name, **args)
|
||||
with salt.utils.files.fopen(tfile.name) as tfile2:
|
||||
assert salt.utils.stringutils.to_unicode(tfile2.read()) == expected
|
||||
os.remove(tfile.name)
|
||||
|
||||
# not appending if matches
|
||||
with salt.utils.files.fopen(str(subdir / "tfile"), "w+b") as tfile:
|
||||
base = os.linesep.join(["foo=1", "baz=42", "bar=2"])
|
||||
tfile.write(salt.utils.stringutils.to_bytes(base))
|
||||
tfile.flush()
|
||||
expected = base
|
||||
filemod.replace(tfile.name, **args)
|
||||
with salt.utils.files.fopen(tfile.name) as tfile2:
|
||||
assert salt.utils.stringutils.to_unicode(tfile2.read()) == expected
|
||||
|
||||
|
||||
def test_backup(multiline_file):
|
||||
fext = ".bak"
|
||||
bak_file = "{}{}".format(multiline_file, fext)
|
||||
|
||||
filemod.replace(multiline_file, r"Etiam", "Salticus", backup=fext)
|
||||
|
||||
assert os.path.exists(bak_file)
|
||||
os.unlink(bak_file)
|
||||
|
||||
|
||||
def test_nobackup(multiline_file):
|
||||
fext = ".bak"
|
||||
bak_file = "{}{}".format(multiline_file, fext)
|
||||
|
||||
filemod.replace(multiline_file, r"Etiam", "Salticus", backup=False)
|
||||
|
||||
assert not os.path.exists(bak_file)
|
||||
|
||||
|
||||
def test_dry_run(multiline_file):
|
||||
before_ctime = os.stat(multiline_file).st_mtime
|
||||
filemod.replace(multiline_file, r"Etiam", "Salticus", dry_run=True)
|
||||
after_ctime = os.stat(multiline_file).st_mtime
|
||||
|
||||
assert before_ctime == after_ctime
|
||||
|
||||
|
||||
def test_show_changes(multiline_file):
|
||||
ret = filemod.replace(multiline_file, r"Etiam", "Salticus", show_changes=True)
|
||||
|
||||
assert ret.startswith("---") # looks like a diff
|
||||
|
||||
|
||||
def test_noshow_changes(multiline_file):
|
||||
ret = filemod.replace(multiline_file, r"Etiam", "Salticus", show_changes=False)
|
||||
|
||||
assert isinstance(ret, bool)
|
||||
|
||||
|
||||
def test_re_str_flags(multiline_file):
|
||||
# upper- & lower-case
|
||||
filemod.replace(
|
||||
multiline_file, r"Etiam", "Salticus", flags=["MULTILINE", "ignorecase"]
|
||||
)
|
||||
|
||||
|
||||
def test_re_int_flags(multiline_file):
|
||||
filemod.replace(multiline_file, r"Etiam", "Salticus", flags=10)
|
||||
|
||||
|
||||
def test_empty_flags_list(multiline_file):
|
||||
filemod.replace(multiline_file, r"Etiam", "Salticus", flags=[])
|
||||
|
||||
|
||||
def test_numeric_repl(multiline_file):
|
||||
"""
|
||||
This test covers cases where the replacement string is numeric, and the
|
||||
CLI parser yamlifies it into a numeric type. If not converted back to a
|
||||
string type in file.replace, a TypeError occurs when the replacemen is
|
||||
attempted. See https://github.com/saltstack/salt/issues/9097 for more
|
||||
information.
|
||||
"""
|
||||
filemod.replace(multiline_file, r"Etiam", 123)
|
||||
|
||||
|
||||
def test_search_only_return_true(multiline_file):
|
||||
ret = filemod.replace(multiline_file, r"Etiam", "Salticus", search_only=True)
|
||||
|
||||
assert isinstance(ret, bool)
|
||||
assert ret is True
|
||||
|
||||
|
||||
def test_search_only_return_false(multiline_file):
|
||||
ret = filemod.replace(multiline_file, r"Etian", "Salticus", search_only=True)
|
||||
|
||||
assert isinstance(ret, bool)
|
||||
assert ret is False
|
Loading…
Add table
Reference in a new issue