import pathlib
import platform
from unittest.mock import Mock
from unittest.mock import patch
from contextlib import nullcontext as does_not_raise
import pytest
from turbo_turtle import _fetch
from turbo_turtle import _settings
testing_windows, root_fs = platform_check()
if testing_windows:
root_directory = pathlib.Path("C:/path/to/source")
destination = pathlib.Path("C:/path/to/destination")
else:
root_directory = pathlib.Path("/path/to/source")
destination = pathlib.Path("/path/to/destination")
source_files = [pathlib.Path("dummy.file1"), pathlib.Path("dummy.file2")]
one_file_source_tree = [root_directory / source_files[0]]
one_file_destination_tree = [destination / source_files[0]]
one_file_copy_tuples = ((one_file_source_tree[0], one_file_destination_tree[0]),)
two_file_source_tree = [root_directory / path for path in source_files]
two_file_destination_tree = [destination / path for path in source_files]
def test_fetch():
# Test the "unreachable" exit code used as a sign-of-life that the installed package structure assumptions in
# _settings.py are correct.
with patch("turbo_turtle._fetch.recursive_copy") as mock_recursive_copy, pytest.raises(RuntimeError):
try:
_fetch.main(
"dummy_subcommand",
pathlib.Path("/directory/assumptions/are/wrong"),
["dummy/relative/path"],
"/dummy/destination",
)
finally:
mock_recursive_copy.assert_not_called()
conditional_copy_input = {
"one new file": (one_file_copy_tuples, [False], [False], one_file_copy_tuples[0]), # File does not exist
"one different file": ( # File does exist, but it's different from the source file
one_file_copy_tuples,
[True],
[False],
one_file_copy_tuples[0],
),
"one identical file": (one_file_copy_tuples, [True], [True], None), # File exists and is identical to source file
"one missing (different) file": ( # File doesn't exist and is identical to source file. Should never actually occur
one_file_copy_tuples,
[False],
[True],
one_file_copy_tuples[0],
),
}
@pytest.mark.parametrize(
"copy_tuples, exists_side_effect, filecmp_side_effect, copyfile_call",
conditional_copy_input.values(),
ids=conditional_copy_input.keys(),
)
def test_conditional_copy(copy_tuples, exists_side_effect, filecmp_side_effect, copyfile_call):
with (
patch("pathlib.Path.exists", side_effect=exists_side_effect),
patch("filecmp.cmp", side_effect=filecmp_side_effect),
patch("pathlib.Path.mkdir") as mock_mkdir,
patch("shutil.copyfile") as mock_copyfile,
):
_fetch.conditional_copy(copy_tuples)
if copyfile_call:
mock_mkdir.assert_called_once()
mock_copyfile.assert_called_once_with(*copyfile_call)
else:
mock_copyfile.assert_not_called()
available_files_input = {
"one file, str": ("/path/to/source", "dummy.file1", [True], [False], [], one_file_source_tree, [], None),
"one file, list": ("/path/to/source", ["dummy.file1"], [True], [False], [], one_file_source_tree, [], None),
"one file, not found": (
"/path/to/source",
["dummy.file1"],
[False],
[False],
[[]],
[],
["dummy.file1"],
"dummy.file1",
),
"two files": (
"/path/to/source",
["dummy.file2", "dummy.file1"],
[True, True],
[],
[],
two_file_source_tree,
[],
None,
),
"one directory, one file": (
"/path/to",
"source",
[False, True],
[True],
[one_file_source_tree],
one_file_source_tree,
[],
"*",
),
"one directory, two files": (
"/path/to",
"source",
[False, True, True],
[True],
[two_file_source_tree],
two_file_source_tree,
[],
"*",
),
"two files, rglob pattern": (
"/path/to/source",
["dummy.file*"],
[False, True, True],
[False],
[two_file_source_tree],
two_file_source_tree,
[],
"dummy.file*",
),
}
@pytest.mark.parametrize(
"root_directory, relative_paths, "
"is_file_side_effect, is_dir_side_effect, rglob_side_effect, "
"expected_files, expected_missing, mock_rglob_argument",
available_files_input.values(),
ids=available_files_input.keys(),
)
def test_available_files(
root_directory,
relative_paths,
is_file_side_effect,
is_dir_side_effect,
rglob_side_effect,
expected_files,
expected_missing,
mock_rglob_argument,
):
with (
patch("pathlib.Path.is_file", side_effect=is_file_side_effect),
patch("pathlib.Path.is_dir", side_effect=is_dir_side_effect),
patch("pathlib.Path.rglob", side_effect=rglob_side_effect) as mock_rglob,
):
available_files, not_found = _fetch.available_files(root_directory, relative_paths)
assert available_files == expected_files
assert not_found == expected_missing
if mock_rglob_argument:
mock_rglob.assert_called_once_with(mock_rglob_argument)
else:
mock_rglob.assert_not_called()
build_source_files_input = {
"one file not matched": (
"/path/to/source",
["dummy.file1"],
["notamatch"],
(one_file_source_tree, []),
one_file_source_tree,
),
"one file matched": ("/path/to/source", ["dummy.file1"], ["dummy"], (one_file_source_tree, []), []),
"two files, one matched": (
"/path/to/source",
["dummy.file1", "matched"],
["matched"],
([one_file_source_tree[0], pathlib.Path("/path/to/source/matched")], []),
one_file_source_tree,
),
}
@pytest.mark.parametrize(
"root_directory, relative_paths, exclude_patterns, " "available_files_side_effect, " "expected_source_files",
build_source_files_input.values(),
ids=build_source_files_input.keys(),
)
def test_build_source_files(
root_directory, relative_paths, exclude_patterns, available_files_side_effect, expected_source_files
):
with patch("turbo_turtle._fetch.available_files", return_value=available_files_side_effect):
source_files, not_found = _fetch.build_source_files(
root_directory, relative_paths, exclude_patterns=exclude_patterns
)
assert source_files == expected_source_files
expected_path = root_directory
longest_common_path_prefix_input = {
"no list": ([], expected_path, pytest.raises(RuntimeError)),
"one file, str": (str(one_file_source_tree[0]), expected_path, pytest.raises(ValueError)),
"one file, path": (one_file_source_tree[0], expected_path, pytest.raises(TypeError)),
"one file, list": (one_file_source_tree, expected_path, does_not_raise()),
"two files": (two_file_source_tree, expected_path, does_not_raise()),
}
@pytest.mark.parametrize(
"file_list, expected_path, outcome",
longest_common_path_prefix_input.values(),
ids=longest_common_path_prefix_input.keys(),
)
def test_longest_common_path_prefix(file_list, expected_path, outcome):
with outcome:
try:
path_prefix = _fetch.longest_common_path_prefix(file_list)
assert path_prefix == expected_path
finally:
pass
build_destination_files_input = {
"two files, one exists": (
"/path/to/destination",
two_file_source_tree,
[False, True],
two_file_destination_tree,
[two_file_destination_tree[1]],
)
}
@pytest.mark.parametrize(
"destination, requested_paths, " "exists_side_effect, " "expected_destination_files, expected_existing_files",
build_destination_files_input.values(),
ids=build_destination_files_input.keys(),
)
def test_build_destination_files(
destination, requested_paths, exists_side_effect, expected_destination_files, expected_existing_files
):
with patch("pathlib.Path.exists", side_effect=exists_side_effect):
destination_files, existing_files = _fetch.build_destination_files(destination, requested_paths)
assert destination_files == expected_destination_files
assert existing_files == expected_existing_files
build_copy_tuples_input = {
"two files, one exists, overwrite": (
"/path/to/destination",
two_file_source_tree,
True,
(two_file_destination_tree, [two_file_destination_tree[1]]),
list(zip(two_file_source_tree, two_file_destination_tree)),
),
"two files, one exists, no overwrite": (
"/path/to/destination",
two_file_source_tree,
False,
(two_file_destination_tree, [two_file_destination_tree[1]]),
list(zip([two_file_source_tree[0]], [two_file_destination_tree[0]])),
),
}
@pytest.mark.parametrize(
"destination, requested_paths_resolved, overwrite, " "build_destination_files_side_effect, " "expected_copy_tuples",
build_copy_tuples_input.values(),
ids=build_copy_tuples_input.keys(),
)
def test_build_copy_tuples(
destination, requested_paths_resolved, overwrite, build_destination_files_side_effect, expected_copy_tuples
):
with patch("turbo_turtle._fetch.build_destination_files", return_value=build_destination_files_side_effect):
copy_tuples = _fetch.build_copy_tuples(destination, requested_paths_resolved, overwrite=overwrite)
assert copy_tuples == expected_copy_tuples
def test_print_list():
# TODO: implement stdout tests
pass
@pytest.mark.parametrize(
"root_directory, source_files, source_tree, destination_tree, tutorial",
[
(root_directory, source_files, two_file_source_tree, two_file_destination_tree, None),
(root_directory, source_files, two_file_source_tree, two_file_destination_tree, 6),
],
)
def test_recursive_copy(root_directory, source_files, source_tree, destination_tree, tutorial):
# Dummy modsim_template tree
copy_tuples = list(zip(source_tree, destination_tree))
not_found = []
available_files_output = (source_tree, not_found)
single_file_requested = ([source_tree[0]], not_found)
# Files in destination tree do not exist. Copy the modsim_template file tree.
with (
patch("turbo_turtle._fetch.available_files", return_value=available_files_output),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[False, False]),
patch("filecmp.cmp", return_value=False),
does_not_raise(),
):
_fetch.recursive_copy(root_directory.parent, root_directory.name, destination)
mock_print_list.assert_not_called()
mock_conditional_copy.assert_called_once_with(copy_tuples)
# Files in destination tree do not exist. Only want the first file. Copy the first file..
with (
patch("turbo_turtle._fetch.available_files", side_effect=[available_files_output, single_file_requested]),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[False, False]),
patch("filecmp.cmp", return_value=False),
does_not_raise(),
):
_fetch.recursive_copy(
root_directory.parent, root_directory.name, destination, requested_paths=[source_files[0]]
)
mock_print_list.assert_not_called()
mock_conditional_copy.assert_called_once_with([copy_tuples[0]])
# Files in destination tree do not exist, but dry-run. Print destination file tree.
with (
patch("turbo_turtle._fetch.available_files", return_value=available_files_output),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[False, False]),
patch("filecmp.cmp", return_value=False),
does_not_raise(),
):
_fetch.recursive_copy(root_directory.parent, root_directory.name, destination, dry_run=True)
mock_print_list.assert_called_once_with(destination_tree)
mock_conditional_copy.assert_not_called()
# Files in destination tree do not exist, but print_available. Print source file tree.
with (
patch("turbo_turtle._fetch.available_files", return_value=available_files_output),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[False, False]),
patch("filecmp.cmp", return_value=False),
does_not_raise(),
):
_fetch.recursive_copy(root_directory.parent, root_directory.name, destination, print_available=True)
mock_print_list.assert_called_once_with(source_files)
mock_conditional_copy.assert_not_called()
# All files in destination tree do exist. Don't copy the modsim_template file tree.
# Don't error out, just remove destination file from the copy list
with (
patch("turbo_turtle._fetch.available_files", return_value=available_files_output),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[True, True]),
patch("filecmp.cmp", return_value=True),
does_not_raise(),
):
_fetch.recursive_copy(root_directory.parent, root_directory.name, destination)
mock_print_list.assert_not_called()
mock_conditional_copy.assert_called_once_with([])
# Files in destination tree do exist, we want to overwrite contents, and the files differ. Copy the source file.
with (
patch("turbo_turtle._fetch.available_files", return_value=available_files_output),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[True, True]),
patch("filecmp.cmp", return_value=False),
does_not_raise(),
):
_fetch.recursive_copy(root_directory.parent, root_directory.name, destination, overwrite=True)
mock_print_list.assert_not_called()
mock_conditional_copy.assert_called_once_with(copy_tuples)
# Files in destination tree do exist, we want to overwrite contents, but the files are the same. Don't copy the
# source file.
with (
patch("turbo_turtle._fetch.available_files", return_value=available_files_output),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[True, True]),
patch("filecmp.cmp", return_value=True),
does_not_raise(),
):
_fetch.recursive_copy(root_directory.parent, root_directory.name, destination, overwrite=True)
mock_print_list.assert_not_called()
mock_conditional_copy.assert_called_once_with(copy_tuples)
# Files in destination tree do exist, but we want to overwrite contents and dry-run.
# Print the modsim_template file tree.
with (
patch("turbo_turtle._fetch.available_files", return_value=available_files_output),
patch("turbo_turtle._fetch.print_list") as mock_print_list,
patch("turbo_turtle._fetch.conditional_copy") as mock_conditional_copy,
patch("pathlib.Path.exists", side_effect=[True, True]),
patch("filecmp.cmp", return_value=True),
does_not_raise(),
):
_fetch.recursive_copy(root_directory.parent, root_directory.name, destination, overwrite=True, dry_run=True)
mock_print_list.assert_called_once_with(destination_tree)
mock_conditional_copy.assert_not_called()