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:
Twangboy 2022-01-13 17:27:55 -07:00 committed by Megan Wilhite
parent 6680407756
commit a35b29b265
9 changed files with 645 additions and 291 deletions

View file

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

View file

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

View file

@ -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:

View file

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

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

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

View 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

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

View file

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