From 26e31d23d525e3271d54d5f2072ee0324c2f1559 Mon Sep 17 00:00:00 2001 From: twangboy Date: Wed, 28 Aug 2024 12:23:41 -0600 Subject: [PATCH 1/6] Manage symlinks after dirs and files Force symlink on keep_symlinks --- salt/states/file.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/salt/states/file.py b/salt/states/file.py index 4adf6e12529b..04c490e97109 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -4668,12 +4668,16 @@ def manage_directory(path): name, source, keep_symlinks, include_pat, exclude_pat, maxdepth, include_empty ) + for dirname in mng_dirs: + manage_directory(dirname) + for dest, src in mng_files: + manage_file(dest, src, replace) for srelpath, ltarget in mng_symlinks: _ret = symlink( os.path.join(name, srelpath), ltarget, makedirs=True, - force=force_symlinks, + force=force_symlinks or keep_symlinks, user=user, group=group, mode=sym_mode, @@ -4681,10 +4685,6 @@ def manage_directory(path): if not _ret: continue merge_ret(os.path.join(name, srelpath), _ret) - for dirname in mng_dirs: - manage_directory(dirname) - for dest, src in mng_files: - manage_file(dest, src, replace) if clean: # TODO: Use directory(clean=True) instead From 572e955388ec8b3550786f1c2b11fa07adb3b824 Mon Sep 17 00:00:00 2001 From: twangboy Date: Fri, 30 Aug 2024 11:55:46 -0600 Subject: [PATCH 2/6] Add tests and changelog --- changelog/64630.fixed.md | 3 + .../functional/states/file/test_recurse.py | 89 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 changelog/64630.fixed.md diff --git a/changelog/64630.fixed.md b/changelog/64630.fixed.md new file mode 100644 index 000000000000..f49c58d4c2e0 --- /dev/null +++ b/changelog/64630.fixed.md @@ -0,0 +1,3 @@ +Fixed an intermittent issue with file.recurse where the state would +report failure even on success. Makes sure symlinks are created +after the target file is created diff --git a/tests/pytests/functional/states/file/test_recurse.py b/tests/pytests/functional/states/file/test_recurse.py index c735d5128dac..53583b26b2d4 100644 --- a/tests/pytests/functional/states/file/test_recurse.py +++ b/tests/pytests/functional/states/file/test_recurse.py @@ -7,6 +7,19 @@ ] +@pytest.fixture(scope="module") +def symlink(state_tree): + # Create directory structure + source_dir = state_tree / "test_symlink" + if not source_dir.is_dir(): + source_dir.mkdir() + source_file = source_dir / "source_file.txt" + source_file.write_text("This is the source file...") + symlink_file = source_dir / "symlink" + symlink_file.symlink_to(source_file) + yield + + @pytest.mark.parametrize("test", (False, True)) def test_recurse(file, tmp_path, grail, test): """ @@ -249,3 +262,79 @@ def test_issue_2726_mode_kwarg(modules, tmp_path, state_tree): ret = modules.state.template_str("\n".join(good_template)) for state_run in ret: assert state_run.result is True + + +def test_issue_64630_keep_symlinks_true(file, symlink, tmp_path): + """ + Make sure that symlinks are created and that there isn't an error + """ + target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + ret = file.recurse( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_symlink() + + +def test_issue_64630_keep_symlinks_false(file, symlink, tmp_path): + """ + Make sure that symlinks are created and that there isn't an error + """ + target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + ret = file.recurse( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=False + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_file() + assert target_file.read_text() == target_symlink.read_text() + + +def test_issue_64630_force_symlinks_true(file, symlink, tmp_path): + """ + Make sure that symlinks are created and that there isn't an error + """ + target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + ret = file.recurse( + name=str(target_dir), source=f"salt://{target_dir.name}", force_symlinks=True + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_file() + + +def test_issue_64630_force_symlinks_keep_symlinks_true(file, symlink, tmp_path): + """ + Make sure that symlinks are created and that there isn't an error + """ + target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + ret = file.recurse( + name=str(target_dir), + source=f"salt://{target_dir.name}", + force_symlinks=True, + keep_symlinks=True, + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_symlink() From bbb0013138bc8d6c5769f6211da5dd9773a3cf82 Mon Sep 17 00:00:00 2001 From: twangboy Date: Fri, 6 Sep 2024 13:54:05 -0600 Subject: [PATCH 3/6] Make sure symlinks are done last --- salt/fileserver/roots.py | 2 +- salt/states/file.py | 23 +++++-- .../functional/states/file/test_recurse.py | 65 +++++++++++++++---- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/salt/fileserver/roots.py b/salt/fileserver/roots.py index e81f37dcf029..cb27396b9790 100644 --- a/salt/fileserver/roots.py +++ b/salt/fileserver/roots.py @@ -325,7 +325,7 @@ def file_hash(load, fnd): def _file_lists(load, form): """ - Return a dict containing the file lists for files, dirs, emtydirs and symlinks + Return a dict containing the file lists for files, dirs, empty dirs and symlinks """ if "env" in load: # "env" is not supported; Use "saltenv". diff --git a/salt/states/file.py b/salt/states/file.py index 04c490e97109..2e943f0797c0 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -557,7 +557,18 @@ def process_symlinks(filenames, symlinks): managed_directories.add(mdest) keep.add(mdest) - return managed_files, managed_directories, managed_symlinks, keep + # Sets are randomly ordered. We need to make sure symlinks are always at the + # end, so we need to use a list + new_managed_files = list(managed_files) + # Now let's move all the symlinks to the end + for symlink in managed_symlinks: + for file in managed_files: + if file[0].endswith(symlink[0]): + new_managed_files.append( + new_managed_files.pop(new_managed_files.index(file)) + ) + + return new_managed_files, managed_directories, managed_symlinks, keep def _gen_keep_files(name, require, walk_d=None): @@ -4668,16 +4679,12 @@ def manage_directory(path): name, source, keep_symlinks, include_pat, exclude_pat, maxdepth, include_empty ) - for dirname in mng_dirs: - manage_directory(dirname) - for dest, src in mng_files: - manage_file(dest, src, replace) for srelpath, ltarget in mng_symlinks: _ret = symlink( os.path.join(name, srelpath), ltarget, makedirs=True, - force=force_symlinks or keep_symlinks, + force=force_symlinks, user=user, group=group, mode=sym_mode, @@ -4685,6 +4692,10 @@ def manage_directory(path): if not _ret: continue merge_ret(os.path.join(name, srelpath), _ret) + for dirname in mng_dirs: + manage_directory(dirname) + for dest, src in mng_files: + manage_file(dest, src, replace) if clean: # TODO: Use directory(clean=True) instead diff --git a/tests/pytests/functional/states/file/test_recurse.py b/tests/pytests/functional/states/file/test_recurse.py index 53583b26b2d4..04370c046afa 100644 --- a/tests/pytests/functional/states/file/test_recurse.py +++ b/tests/pytests/functional/states/file/test_recurse.py @@ -8,16 +8,35 @@ @pytest.fixture(scope="module") -def symlink(state_tree): +def symlink_scenario_1(state_tree): # Create directory structure - source_dir = state_tree / "test_symlink" + dir_name = "symlink_scenario_1" + source_dir = state_tree / dir_name if not source_dir.is_dir(): source_dir.mkdir() source_file = source_dir / "source_file.txt" source_file.write_text("This is the source file...") symlink_file = source_dir / "symlink" symlink_file.symlink_to(source_file) - yield + yield dir_name + + +@pytest.fixture(scope="module") +def symlink_scenario_2(state_tree): + # Create directory structure + dir_name = "symlink_scenario_2" + source_dir = state_tree / dir_name / "test" + if not source_dir.is_dir(): + source_dir.mkdir(parents=True) + test1 = source_dir / "test1" + test2 = source_dir / "test2" + test3 = source_dir / "test3" + test_link = source_dir / "test" + test1.touch() + test2.touch() + test3.touch() + test_link.symlink_to(test3) + yield dir_name @pytest.mark.parametrize("test", (False, True)) @@ -264,11 +283,11 @@ def test_issue_2726_mode_kwarg(modules, tmp_path, state_tree): assert state_run.result is True -def test_issue_64630_keep_symlinks_true(file, symlink, tmp_path): +def test_issue_64630_keep_symlinks_true(file, symlink_scenario_1, tmp_path): """ Make sure that symlinks are created and that there isn't an error """ - target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" target_symlink = target_dir / "symlink" @@ -282,11 +301,11 @@ def test_issue_64630_keep_symlinks_true(file, symlink, tmp_path): assert target_symlink.is_symlink() -def test_issue_64630_keep_symlinks_false(file, symlink, tmp_path): +def test_issue_64630_keep_symlinks_false(file, symlink_scenario_1, tmp_path): """ Make sure that symlinks are created and that there isn't an error """ - target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" target_symlink = target_dir / "symlink" @@ -301,11 +320,11 @@ def test_issue_64630_keep_symlinks_false(file, symlink, tmp_path): assert target_file.read_text() == target_symlink.read_text() -def test_issue_64630_force_symlinks_true(file, symlink, tmp_path): +def test_issue_64630_force_symlinks_true(file, symlink_scenario_1, tmp_path): """ Make sure that symlinks are created and that there isn't an error """ - target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" target_symlink = target_dir / "symlink" @@ -319,11 +338,13 @@ def test_issue_64630_force_symlinks_true(file, symlink, tmp_path): assert target_symlink.is_file() -def test_issue_64630_force_symlinks_keep_symlinks_true(file, symlink, tmp_path): +def test_issue_64630_force_symlinks_keep_symlinks_true( + file, symlink_scenario_1, tmp_path +): """ Make sure that symlinks are created and that there isn't an error """ - target_dir = tmp_path / "test_symlink" # Target for the file.recurse state + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" target_symlink = target_dir / "symlink" @@ -338,3 +359,25 @@ def test_issue_64630_force_symlinks_keep_symlinks_true(file, symlink, tmp_path): assert target_dir.exists() assert target_file.is_file() assert target_symlink.is_symlink() + + +def test_issue_62117(file, symlink_scenario_2, tmp_path): + target_dir = tmp_path / symlink_scenario_2 / "test" + target_file_1 = target_dir / "test1" + target_file_2 = target_dir / "test2" + target_file_3 = target_dir / "test3" + target_symlink = target_dir / "test" + + ret = file.recurse( + name=str(target_dir), + source=f"salt://{target_dir.parent.name}/test", + clean=True, + keep_symlinks=True, + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file_1.is_file() + assert target_file_2.is_file() + assert target_file_3.is_file() + assert target_symlink.is_symlink() From 294a81a05c1c941a0bde2b376df14e6285f85556 Mon Sep 17 00:00:00 2001 From: twangboy Date: Wed, 11 Sep 2024 14:58:41 -0600 Subject: [PATCH 4/6] Try to handle similar names --- salt/states/file.py | 30 ++++--- .../functional/states/file/test_recurse.py | 83 +++++++++++++++++-- 2 files changed, 94 insertions(+), 19 deletions(-) diff --git a/salt/states/file.py b/salt/states/file.py index 2e943f0797c0..717dafb47200 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -563,7 +563,7 @@ def process_symlinks(filenames, symlinks): # Now let's move all the symlinks to the end for symlink in managed_symlinks: for file in managed_files: - if file[0].endswith(symlink[0]): + if file[0].endswith(os.sep + symlink[0]): new_managed_files.append( new_managed_files.pop(new_managed_files.index(file)) ) @@ -4443,18 +4443,26 @@ def recurse( or immediate subdirectories keep_symlinks - Keep symlinks when copying from the source. This option will cause - the copy operation to terminate at the symlink. If desire behavior - similar to rsync, then set this to True. This option is not taken - in account if ``fileserver_followsymlinks`` is set to False. + + Determines how symbolic links (symlinks) are handled during the copying + process. When set to ``True``, the copy operation will copy the symlink + itself, rather than the file or directory it points to. When set to + ``False``, the operation will follow the symlink and copy the target + file or directory. If you want behavior similar to rsync, set this + option to ``True``. + + However, if the ``fileserver_followsymlinks`` option is set to ``False``, + the ``keep_symlinks`` setting will be ignored, and symlinks will not be + copied at all. force_symlinks - Force symlink creation. This option will force the symlink creation. - If a file or directory is obstructing symlink creation it will be - recursively removed so that symlink creation can proceed. This - option is usually not needed except in special circumstances. This - option is not taken in account if ``fileserver_followsymlinks`` is - set to False. + + Controls the creation of symlinks when using ``keep_symlinks``. When set + to ``True``, it forces the creation of symlinks by removing any existing + files or directories that might be obstructing their creation. This + removal is done recursively if a directory is blocking the symlink. This + option is only used when ``keep_symlinks`` is passed and is ignored if + ``fileserver_followsymlinks`` is set to ``False``. win_owner The owner of the symlink and directories if ``makedirs`` is True. If diff --git a/tests/pytests/functional/states/file/test_recurse.py b/tests/pytests/functional/states/file/test_recurse.py index 04370c046afa..9b69bbf5fffd 100644 --- a/tests/pytests/functional/states/file/test_recurse.py +++ b/tests/pytests/functional/states/file/test_recurse.py @@ -39,6 +39,28 @@ def symlink_scenario_2(state_tree): yield dir_name +@pytest.fixture(scope="module") +def symlink_scenario_3(state_tree): + # Create directory structure + dir_name = "symlink_scenario_3" + source_dir = state_tree / dir_name + if not source_dir.is_dir(): + source_dir.mkdir(parents=True) + # Create a file with the same name but is not a symlink + source_file = source_dir / "not_a_symlink" / "symlink" + source_file.parent.mkdir(parents=True) + source_file.write_text("This is the source file...") + # Create other fluff files + just_a_file = source_dir / "just_a_file.txt" + just_a_file.touch() + dummy_file = source_dir / "notasymlink" + dummy_file.touch() + # Create symlink to source with the same name + symlink_file = source_dir / "symlink" + symlink_file.symlink_to(source_file) + yield dir_name + + @pytest.mark.parametrize("test", (False, True)) def test_recurse(file, tmp_path, grail, test): """ @@ -285,7 +307,8 @@ def test_issue_2726_mode_kwarg(modules, tmp_path, state_tree): def test_issue_64630_keep_symlinks_true(file, symlink_scenario_1, tmp_path): """ - Make sure that symlinks are created and that there isn't an error + Make sure that symlinks are created and that there isn't an error when there + are no conflicting target files """ target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" @@ -303,7 +326,7 @@ def test_issue_64630_keep_symlinks_true(file, symlink_scenario_1, tmp_path): def test_issue_64630_keep_symlinks_false(file, symlink_scenario_1, tmp_path): """ - Make sure that symlinks are created and that there isn't an error + Make sure that symlinks are created as files and that there isn't an error """ target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" @@ -320,34 +343,53 @@ def test_issue_64630_keep_symlinks_false(file, symlink_scenario_1, tmp_path): assert target_file.read_text() == target_symlink.read_text() -def test_issue_64630_force_symlinks_true(file, symlink_scenario_1, tmp_path): +def test_issue_64630_keep_symlinks_conflicting_force_symlinks_false( + file, symlink_scenario_1, tmp_path +): """ - Make sure that symlinks are created and that there isn't an error + Make sure that symlinks are not created when there is a conflict. The state + should return False """ target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" target_symlink = target_dir / "symlink" + # Create the conflicting file + target_symlink.parent.mkdir(parents=True) + target_symlink.touch() + assert target_symlink.is_file() + ret = file.recurse( - name=str(target_dir), source=f"salt://{target_dir.name}", force_symlinks=True + name=str(target_dir), + source=f"salt://{target_dir.name}", + keep_symlinks=True, + force_symlinks=False, ) - assert ret.result is True + # We expect it to fail + assert ret.result is False + # And files not to be created properly assert target_dir.exists() assert target_file.is_file() assert target_symlink.is_file() -def test_issue_64630_force_symlinks_keep_symlinks_true( +def test_issue_64630_keep_symlinks_conflicting_force_symlinks_true( file, symlink_scenario_1, tmp_path ): """ - Make sure that symlinks are created and that there isn't an error + Make sure that symlinks are created when there is a conflict with an + existing file. """ target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state target_file = target_dir / "source_file.txt" target_symlink = target_dir / "symlink" + # Create the conflicting file + target_symlink.parent.mkdir(parents=True) + target_symlink.touch() + assert target_symlink.is_file() + ret = file.recurse( name=str(target_dir), source=f"salt://{target_dir.name}", @@ -361,6 +403,31 @@ def test_issue_64630_force_symlinks_keep_symlinks_true( assert target_symlink.is_symlink() +def test_issue_64630_keep_symlinks_similar_names(file, symlink_scenario_3, tmp_path): + """ + Make sure that symlinks are created when there is a file that shares part + of the name of the actual symlink file. I'm not sure what I'm testing here + as I couldn't really get this to fail either way + """ + target_dir = tmp_path / symlink_scenario_3 # Target for the file.recurse state + # symlink target, but has the same name as the symlink itself + target_source = target_dir / "not_a_symlink" / "symlink" + target_symlink = target_dir / "symlink" + decoy_file = target_dir / "notasymlink" + just_a_file = target_dir / "just_a_file.txt" + + ret = file.recurse( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True + ) + assert ret.result is True + + assert target_dir.exists() + assert target_source.is_file() + assert decoy_file.is_file() + assert just_a_file.is_file() + assert target_symlink.is_symlink() + + def test_issue_62117(file, symlink_scenario_2, tmp_path): target_dir = tmp_path / symlink_scenario_2 / "test" target_file_1 = target_dir / "test1" From 554da0c21efa1e891bc21ed25bd9e142782c35f0 Mon Sep 17 00:00:00 2001 From: twangboy Date: Thu, 12 Sep 2024 09:49:19 -0600 Subject: [PATCH 5/6] Compare full paths --- salt/states/file.py | 21 +++++++--- .../pytests/unit/states/file/test_recurse.py | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 tests/pytests/unit/states/file/test_recurse.py diff --git a/salt/states/file.py b/salt/states/file.py index 717dafb47200..e4d9b7ece985 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -282,6 +282,7 @@ def run(): import itertools import logging import os +import pathlib import posixpath import re import shutil @@ -557,15 +558,23 @@ def process_symlinks(filenames, symlinks): managed_directories.add(mdest) keep.add(mdest) - # Sets are randomly ordered. We need to make sure symlinks are always at the - # end, so we need to use a list + # Sets are randomly ordered. We need to use a list so we can make sure + # symlinks are always at the end. This is necessary because the file must + # exist before we can create a symlink to it. See issue: + # https://github.com/saltstack/salt/issues/64630 new_managed_files = list(managed_files) # Now let's move all the symlinks to the end - for symlink in managed_symlinks: - for file in managed_files: - if file[0].endswith(os.sep + symlink[0]): + for link_src_relpath, _ in managed_symlinks: + for file_dest, file_src in managed_files: + # We need to convert relpath to fullpath. We're using pathlib to + # be platform-agnostic + symlink_full_path = pathlib.Path(f"{name}\\{link_src_relpath}") + file_dest_full_path = pathlib.Path(file_dest) + if symlink_full_path == file_dest_full_path: new_managed_files.append( - new_managed_files.pop(new_managed_files.index(file)) + new_managed_files.pop( + new_managed_files.index((file_dest, file_src)) + ) ) return new_managed_files, managed_directories, managed_symlinks, keep diff --git a/tests/pytests/unit/states/file/test_recurse.py b/tests/pytests/unit/states/file/test_recurse.py new file mode 100644 index 000000000000..dbbf1e1d2fec --- /dev/null +++ b/tests/pytests/unit/states/file/test_recurse.py @@ -0,0 +1,42 @@ +import logging +import pathlib + +import pytest + +import salt.states.file as filestate +from tests.support.mock import MagicMock, patch + +log = logging.getLogger(__name__) + + +@pytest.fixture +def configure_loader_modules(): + return {filestate: {"__salt__": {}, "__opts__": {}, "__env__": "base"}} + + +def test__gen_recurse_managed_files(): + """ + Test _gen_recurse_managed_files to make sure it puts symlinks at the end of the list of files. + """ + target_dir = pathlib.Path("\\some\\path\\target") + cp_list_master = MagicMock( + return_value=[ + "target/symlink", + "target/just_a_file.txt", + "target/not_a_symlink/symlink", + "target/notasymlink", + ], + ) + cp_list_master_symlinks = MagicMock( + return_value={"target/symlink": f"{target_dir}\\not_a_symlink\\symlink"} + ) + patch_salt = { + "cp.list_master": cp_list_master, + "cp.list_master_symlinks": cp_list_master_symlinks, + } + with patch.dict(filestate.__salt__, patch_salt): + files, dirs, links, keep = filestate._gen_recurse_managed_files( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True + ) + expected = ("\\some\\path\\target\\symlink", "salt://target/symlink?saltenv=base") + assert files[-1] == expected From a5c3c184423b76823c8c35c147be58677077144a Mon Sep 17 00:00:00 2001 From: twangboy Date: Thu, 12 Sep 2024 13:27:44 -0600 Subject: [PATCH 6/6] Fix unit test on Linux --- salt/states/file.py | 2 +- tests/pytests/unit/states/file/test_recurse.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/salt/states/file.py b/salt/states/file.py index e4d9b7ece985..6d8c43b02b08 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -568,7 +568,7 @@ def process_symlinks(filenames, symlinks): for file_dest, file_src in managed_files: # We need to convert relpath to fullpath. We're using pathlib to # be platform-agnostic - symlink_full_path = pathlib.Path(f"{name}\\{link_src_relpath}") + symlink_full_path = pathlib.Path(f"{name}{os.sep}{link_src_relpath}") file_dest_full_path = pathlib.Path(file_dest) if symlink_full_path == file_dest_full_path: new_managed_files.append( diff --git a/tests/pytests/unit/states/file/test_recurse.py b/tests/pytests/unit/states/file/test_recurse.py index dbbf1e1d2fec..53e6e0fd22f1 100644 --- a/tests/pytests/unit/states/file/test_recurse.py +++ b/tests/pytests/unit/states/file/test_recurse.py @@ -1,4 +1,5 @@ import logging +import os import pathlib import pytest @@ -18,7 +19,7 @@ def test__gen_recurse_managed_files(): """ Test _gen_recurse_managed_files to make sure it puts symlinks at the end of the list of files. """ - target_dir = pathlib.Path("\\some\\path\\target") + target_dir = pathlib.Path(f"{os.sep}some{os.sep}path{os.sep}target") cp_list_master = MagicMock( return_value=[ "target/symlink", @@ -28,7 +29,9 @@ def test__gen_recurse_managed_files(): ], ) cp_list_master_symlinks = MagicMock( - return_value={"target/symlink": f"{target_dir}\\not_a_symlink\\symlink"} + return_value={ + "target/symlink": f"{target_dir}{os.sep}not_a_symlink{os.sep}symlink" + } ) patch_salt = { "cp.list_master": cp_list_master, @@ -38,5 +41,8 @@ def test__gen_recurse_managed_files(): files, dirs, links, keep = filestate._gen_recurse_managed_files( name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True ) - expected = ("\\some\\path\\target\\symlink", "salt://target/symlink?saltenv=base") + expected = ( + f"{os.sep}some{os.sep}path{os.sep}target{os.sep}symlink", + "salt://target/symlink?saltenv=base", + ) assert files[-1] == expected