You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
21 KiB
Python
477 lines
21 KiB
Python
#
|
|
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
#
|
|
#
|
|
|
|
|
|
import hashlib
|
|
import os
|
|
import pytest
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
ROOT_DEV_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
if ROOT_DEV_PATH not in sys.path:
|
|
sys.path.append(ROOT_DEV_PATH)
|
|
|
|
from cmake.Tools import common, layout_tool
|
|
|
|
|
|
def test_copy_asset_files_to_layout_success():
|
|
|
|
# Mock functions and preserve the originals to restore
|
|
old_os_listdir = os.listdir
|
|
old_os_path_isdir = os.path.isdir
|
|
old_os_path_isfile = os.path.isfile
|
|
old_common_filefingerprint = common.file_fingerprint
|
|
old_shutil_copy2 = shutil.copy2
|
|
|
|
try:
|
|
# Setup test vectors
|
|
|
|
# Denied files, should not show up in the result
|
|
test_denylist_file = [
|
|
'assetprocessorplatformconfig.setreg'
|
|
]
|
|
# System files that are not the same platform, so should skip
|
|
test_skip_system_files = [
|
|
'system_badplatform_pc',
|
|
'system_badplatform_badplatform'
|
|
]
|
|
# Source 'folders' will be skipped
|
|
test_skip_source_folders = [
|
|
'src_folder'
|
|
]
|
|
# Destination 'folders' will be skipped
|
|
test_skip_dest_is_folder = [
|
|
'fake_dst_folder'
|
|
]
|
|
# Skip files that are the same in the destination
|
|
test_dest_same_as_src = [
|
|
'dst_same_as_src'
|
|
]
|
|
# COPY files that are the same in the destination
|
|
test_dest_diff_as_src = [
|
|
'dst_diff_as_src'
|
|
]
|
|
# COPY files that are not in the destination
|
|
test_src_not_in_dst = [
|
|
'system_goodplatform_pc',
|
|
'good_src_1',
|
|
'good_src_2'
|
|
]
|
|
test_expected_copied_files = test_dest_diff_as_src + test_src_not_in_dst
|
|
|
|
test_game_asset_folder = 'game_cache'
|
|
test_layout_target = 'layout_target'
|
|
test_platform = 'goodplatform'
|
|
|
|
def _mock_os_listdir(path):
|
|
assert path == test_game_asset_folder
|
|
mock_files = test_denylist_file + \
|
|
test_skip_system_files + \
|
|
test_skip_source_folders + \
|
|
test_skip_dest_is_folder + \
|
|
test_dest_same_as_src + \
|
|
test_dest_diff_as_src + \
|
|
test_src_not_in_dst
|
|
return mock_files
|
|
os.listdir = _mock_os_listdir
|
|
|
|
def _mock_os_path_isdir(path):
|
|
basename = os.path.basename(path)
|
|
if basename in test_skip_source_folders:
|
|
return True
|
|
if basename in test_skip_dest_is_folder:
|
|
return True
|
|
return False
|
|
os.path.isdir = _mock_os_path_isdir
|
|
|
|
def _mock_os_path_isfile(path):
|
|
basename = os.path.basename(path)
|
|
if basename in test_dest_same_as_src:
|
|
return True
|
|
if basename in test_dest_diff_as_src:
|
|
return True
|
|
return False
|
|
os.path.isfile = _mock_os_path_isfile
|
|
|
|
def _mock_common_file_fingerprint(path):
|
|
basename = os.path.basename(path)
|
|
dirname = os.path.dirname(path)
|
|
if basename in test_dest_same_as_src:
|
|
return "SOURCE_FINGERPRINT"
|
|
elif basename in test_dest_diff_as_src:
|
|
if dirname == test_game_asset_folder:
|
|
return "SOURCE_FINGERPRINT"
|
|
else:
|
|
return "TARGET_FINGERPRINT"
|
|
else:
|
|
assert False
|
|
common.file_fingerprint = _mock_common_file_fingerprint
|
|
|
|
result_copy_files = []
|
|
|
|
def _mock_shutil_copy2(src, dst):
|
|
assert os.path.basename(src) == os.path.basename(dst)
|
|
basename = os.path.basename(dst)
|
|
result_copy_files.append(basename)
|
|
shutil.copy2 = _mock_shutil_copy2
|
|
|
|
layout_tool.copy_asset_files_to_layout(project_asset_folder=test_game_asset_folder,
|
|
target_platform=test_platform,
|
|
layout_target=test_layout_target)
|
|
|
|
assert len(test_expected_copied_files) == len(result_copy_files)
|
|
for expected_copied_file in test_expected_copied_files:
|
|
assert expected_copied_file in result_copy_files
|
|
|
|
finally:
|
|
os.listdir = old_os_listdir
|
|
os.path.isdir = old_os_path_isdir
|
|
os.path.isfile = old_os_path_isfile
|
|
common.file_fingerprint = old_common_filefingerprint
|
|
shutil.copy2 = old_shutil_copy2
|
|
|
|
|
|
def test_create_link_windows_success():
|
|
old_platform = layout_tool.PLATFORM_NAME
|
|
old_subprocess_check_call = subprocess.check_call
|
|
try:
|
|
layout_tool.PLATFORM_NAME = 'Windows'
|
|
|
|
src = "test_src"
|
|
dst = "test_dst"
|
|
|
|
expected = ['cmd', '/c', 'mklink', '/J', dst, src]
|
|
|
|
def _mock_subprocess_check_call(args, stdout=None, stderr=None):
|
|
assert args == expected
|
|
subprocess.check_call = _mock_subprocess_check_call
|
|
|
|
layout_tool.create_link(src, dst, False)
|
|
|
|
finally:
|
|
layout_tool.PLATFORM_NAME = old_platform
|
|
subprocess.check_call = old_subprocess_check_call
|
|
|
|
|
|
def test_create_link_mac_success():
|
|
old_platform = layout_tool.PLATFORM_NAME
|
|
old_subprocess_check_call = subprocess.check_call
|
|
try:
|
|
layout_tool.PLATFORM_NAME = 'Darwin'
|
|
|
|
src = "test_src"
|
|
dst = "test_dst"
|
|
|
|
expected = ['ln', '-s', src, dst]
|
|
|
|
def _mock_subprocess_check_call(args, stdout=None, stderr=None):
|
|
assert args == expected
|
|
subprocess.check_call = _mock_subprocess_check_call
|
|
|
|
layout_tool.create_link(src, dst, False)
|
|
finally:
|
|
layout_tool.PLATFORM_NAME = old_platform
|
|
subprocess.check_call = old_subprocess_check_call
|
|
|
|
|
|
def test_create_link_error():
|
|
old_platform = layout_tool.PLATFORM_NAME
|
|
old_subprocess_check_call = subprocess.check_call
|
|
try:
|
|
layout_tool.PLATFORM_NAME = 'Windows'
|
|
|
|
src = "test_src"
|
|
dst = "test_dst"
|
|
|
|
def _mock_subprocess_check_call(args, stdout=None, stderr=None):
|
|
raise subprocess.CalledProcessError(1, "Bad Call")
|
|
|
|
subprocess.check_call = _mock_subprocess_check_call
|
|
|
|
layout_tool.create_link(src, dst, False)
|
|
|
|
except common.LmbrCmdError:
|
|
pass
|
|
else:
|
|
assert False, "subprocess.CalledProcessError exception expected"
|
|
|
|
finally:
|
|
layout_tool.PLATFORM_NAME = old_platform
|
|
subprocess.check_call = old_subprocess_check_call
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"project_path, asset_type, warn_on_missing, expected_result", [
|
|
pytest.param('Foo', 'pc', False, 'Foo/Cache/pc'),
|
|
pytest.param('Foo', 'pc', True, None),
|
|
pytest.param('Foo', 'pc', True, None),
|
|
pytest.param('Foo', 'pc', False, common.LmbrCmdError),
|
|
pytest.param('Foo', 'pc', False, common.LmbrCmdError),
|
|
]
|
|
)
|
|
def test_construct_and_validate_cache_game_asset_folder_success(tmpdir, project_path, asset_type, warn_on_missing, expected_result):
|
|
if isinstance(expected_result, str):
|
|
expected_path_realpath = str(tmpdir.join(expected_result).realpath())
|
|
elif expected_result == common.LmbrCmdError:
|
|
expected_path_realpath = common.LmbrCmdError
|
|
else:
|
|
expected_path_realpath = None
|
|
|
|
try:
|
|
result = layout_tool.construct_and_validate_cache_project_asset_folder(project_path=project_path,
|
|
asset_type=asset_type,
|
|
warn_on_missing_project_cache=warn_on_missing)
|
|
|
|
assert expected_result != common.LmbrCmdError, "Expecting an error result"
|
|
if result == None:
|
|
assert warn_on_missing, "Expecting a warn_on_missing==True if None is returned"
|
|
elif isinstance(expected_result, str):
|
|
assert os.path.normcase(result) == os.path.normcase(expected_path_realpath)
|
|
|
|
except common.LmbrCmdError:
|
|
assert expected_result == common.LmbrCmdError
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"existing_temp_vfs_folder, existing_gems_link, existing_game_link", [
|
|
pytest.param(False, False, False),
|
|
pytest.param(True, False, False),
|
|
pytest.param(False, True, False),
|
|
pytest.param(True, True, False),
|
|
pytest.param(False, False, True),
|
|
pytest.param(True, False, True),
|
|
pytest.param(False, True, True),
|
|
pytest.param(True, True, True)
|
|
]
|
|
)
|
|
def test_sync_layout_vfs_success(tmpdir, existing_temp_vfs_folder, existing_gems_link, existing_game_link):
|
|
|
|
old_tempfile_gettempdir = tempfile.gettempdir
|
|
old_create_link = layout_tool.create_link
|
|
old_copy_asset_files_to_layout = layout_tool.copy_asset_files_to_layout
|
|
old_rmdir = os.rmdir
|
|
old_unlink = os.unlink
|
|
|
|
try:
|
|
# Simple Test Parameters
|
|
test_engine_root = str(tmpdir.join('engine-root').realpath())
|
|
test_project_path = str(tmpdir.join('Foo').realpath())
|
|
test_project_name_lower = 'foo'
|
|
test_target_platform = 'bogus'
|
|
test_asset_type = 'pc'
|
|
|
|
# Setup a test dev and game cache folder structure inside the temp folder
|
|
|
|
# Capture relevant real paths in the temp folder so we can verify our assertions
|
|
cache_game_folder = os.path.join(test_project_path, 'Cache')
|
|
|
|
cache_game_folder_gems = os.path.join(cache_game_folder, test_asset_type, 'gems')
|
|
|
|
layout_target_root_realpath = str(tmpdir.join('layout').realpath())
|
|
layout_target_gems_realpath = os.path.join(layout_target_root_realpath, 'gems')
|
|
layout_target_game_realpath = os.path.join(layout_target_root_realpath)
|
|
|
|
# If we are optionally testing existing links in a layout folder, track the expected and actual rmdirs
|
|
actual_rmdir_paths = set()
|
|
expected_rmdir_paths = set()
|
|
|
|
# The rmdir will serve as a wrapper to track the paths that are actually deleted
|
|
def _mock_os_rmdir(path):
|
|
actual_rmdir_paths.add(os.path.normcase(path))
|
|
old_rmdir(path)
|
|
def _mock_os_unlink(link):
|
|
actual_rmdir_paths.add(os.path.normcase(link))
|
|
|
|
if existing_gems_link:
|
|
# Optionally make a dummy folder for the target layout for gems and add it to the expected folder to delete
|
|
os.makedirs(layout_target_gems_realpath, exist_ok=False)
|
|
expected_rmdir_paths.add(os.path.normcase(layout_target_gems_realpath))
|
|
os.rmdir = _mock_os_rmdir
|
|
os.unlink = _mock_os_unlink
|
|
|
|
if existing_game_link:
|
|
# Optionally make a dummy folder for the target layout for the game folder and add it to the expected folder to delete
|
|
os.makedirs(layout_target_game_realpath, exist_ok=False)
|
|
expected_rmdir_paths.add(os.path.normcase(layout_target_game_realpath))
|
|
os.rmdir = _mock_os_rmdir
|
|
os.unlink = _mock_os_unlink
|
|
|
|
def _mock_gettempdir():
|
|
# mock tempfile.gettempdir() to use tmpdir from pytest
|
|
return str(tmpdir.realpath())
|
|
tempfile.gettempdir = _mock_gettempdir
|
|
|
|
# Predict the temp folder name
|
|
hasher = hashlib.md5()
|
|
hasher.update(test_project_path.encode('UTF-8'))
|
|
result = hasher.hexdigest()
|
|
tmp_folder_subfolder = 'ly-layout-{}'.format(result)
|
|
test_layout_folder = str(tmpdir.join('{}/vfs/foo'.format(tmp_folder_subfolder)).realpath())
|
|
test_layout_config_folder = str(tmpdir.join('{}/vfs/foo/config'.format(tmp_folder_subfolder)).realpath())
|
|
test_override_pak_folder = ''
|
|
|
|
if existing_temp_vfs_folder:
|
|
# Optionally make a dummy folder for the temp vfs and add the test layout folder and its child config to the expected folders to delete
|
|
os.makedirs(test_layout_config_folder, exist_ok=False)
|
|
expected_rmdir_paths.add(os.path.normcase(test_layout_folder))
|
|
expected_rmdir_paths.add(os.path.normcase(test_layout_config_folder))
|
|
os.rmdir = _mock_os_rmdir
|
|
|
|
mock_layout_tool_create_link_validation = {
|
|
os.path.normcase(cache_game_folder_gems): os.path.normcase(layout_target_gems_realpath),
|
|
os.path.normcase(test_layout_folder): os.path.normcase(layout_target_game_realpath)
|
|
}
|
|
|
|
def _mock_layout_tool_create_link(src, dst, copy):
|
|
check_src = os.path.normcase(src)
|
|
check_dst = os.path.normcase(dst)
|
|
assert check_src in mock_layout_tool_create_link_validation, "Unexpected create link call to {}->{}".format(src, dst)
|
|
assert mock_layout_tool_create_link_validation[check_src] == check_dst, "Assertion on create linked failed: {}->{}".format(src, dst)
|
|
|
|
layout_tool.create_link = _mock_layout_tool_create_link
|
|
|
|
def _mock_copy_asset_files_to_layout(project_path, project_asset_folder, target_platform, layout_target):
|
|
# Validate the correct call to copy asset files
|
|
assert target_platform == target_platform
|
|
assert os.path.normcase(layout_target) == os.path.normcase(layout_target_root_realpath)
|
|
layout_tool.copy_asset_files_to_layout = _mock_copy_asset_files_to_layout
|
|
|
|
layout_tool.sync_layout_vfs(target_platform = test_target_platform,
|
|
project_path = test_project_path,
|
|
asset_type = test_asset_type,
|
|
warning_on_missing_assets = False,
|
|
layout_target = layout_target_root_realpath,
|
|
override_pak_folder = test_override_pak_folder,
|
|
copy = False)
|
|
|
|
# Verify if any the rmdir calls based on the test parameters
|
|
assert actual_rmdir_paths == expected_rmdir_paths
|
|
|
|
finally:
|
|
tempfile.gettempdir = old_tempfile_gettempdir
|
|
layout_tool.create_link = old_create_link
|
|
layout_tool.copy_asset_files_to_layout = old_copy_asset_files_to_layout
|
|
os.rmdir = old_rmdir
|
|
os.unlink = old_unlink
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"mode, existing_game_link, existing_gems_link, test_override_pak_folder", [
|
|
pytest.param("LOOSE", False, False, None),
|
|
pytest.param("LOOSE", False, True, None),
|
|
pytest.param("LOOSE", True, False, None),
|
|
pytest.param("LOOSE", True, True, None),
|
|
pytest.param("PAK", False, None, None),
|
|
pytest.param("PAK", True, None, None),
|
|
pytest.param("PAK", False, None, 'override_paks'),
|
|
pytest.param("PAK", True, None, 'override_paks')
|
|
]
|
|
)
|
|
def test_sync_layout_non_vfs_success(tmpdir, mode, existing_game_link, existing_gems_link, test_override_pak_folder):
|
|
old_rmdir = os.rmdir
|
|
old_copy_asset_files_to_layout = layout_tool.copy_asset_files_to_layout
|
|
old_remove_link = layout_tool.remove_link
|
|
try:
|
|
# Simple Test Parameters
|
|
engine_root_realpath = str(tmpdir.join('engine-root').realpath())
|
|
test_project_path = str(tmpdir.join('Foo').realpath())
|
|
test_project_name_lower = 'foo'
|
|
test_target_platform = 'bogus'
|
|
test_asset_type = 'pc'
|
|
cache_game_folder_realpath = os.path.join(test_project_path, 'Cache')
|
|
|
|
# Make sure a dummy layout folder is created
|
|
tmpdir.ensure('layout/dummy.txt')
|
|
test_layout_target_realpath = str(tmpdir.join('layout').realpath())
|
|
test_layout_target_gems_realpath = os.path.join(test_layout_target_realpath, 'gems')
|
|
test_layout_target_game_realpath = os.path.join(test_layout_target_realpath,)
|
|
|
|
# If we are optionally testing existing links in a layout folder, track the expected and actual rmdirs
|
|
actual_rmdir_paths = set()
|
|
expected_rmdir_paths = set()
|
|
|
|
def _mock_remove_link(link):
|
|
actual_rmdir_paths.add(os.path.normcase(link))
|
|
layout_tool.remove_link = _mock_remove_link
|
|
|
|
# The rmdir will serve as a wrapper to track the paths that are actually deleted
|
|
def _mock_os_rmdir(path):
|
|
actual_rmdir_paths.add(os.path.normcase(path))
|
|
old_rmdir(path)
|
|
|
|
if existing_game_link:
|
|
# Optionally make a dummy folder for the target layout for the game folder and add it to the expected folder to delete
|
|
os.makedirs(test_layout_target_game_realpath, exist_ok=False)
|
|
expected_rmdir_paths.add(os.path.normcase(test_layout_target_game_realpath))
|
|
os.rmdir = _mock_os_rmdir
|
|
|
|
mock_layout_tool_create_link_validation = {}
|
|
if mode == 'PAK':
|
|
# In PAK Mode, the linking rules are slightly different. The 'game folder' link points to inside the pak folder, and there is no 'gems' link
|
|
if test_override_pak_folder:
|
|
test_game_asset_folder = os.path.join(engine_root_realpath, test_override_pak_folder,
|
|
f'{test_project_name_lower}_{test_asset_type}_paks')
|
|
cache_game_folder_game_realpath = os.path.join(test_game_asset_folder)
|
|
else:
|
|
test_game_asset_folder = os.path.join(engine_root_realpath, 'Pak',
|
|
f'{test_project_name_lower}_{test_asset_type}_paks')
|
|
cache_game_folder_game_realpath = os.path.join(test_game_asset_folder)
|
|
|
|
mock_layout_tool_create_link_validation[os.path.normcase(cache_game_folder_game_realpath)] = os.path.normcase(test_layout_target_game_realpath)
|
|
|
|
elif mode == "LOOSE":
|
|
# In LOOSE Mode, both game and gems will be linked
|
|
|
|
if existing_gems_link:
|
|
# Optionally make a dummy folder for the target layout for gems and add it to the expected folder to delete
|
|
os.makedirs(test_layout_target_gems_realpath, exist_ok=False)
|
|
expected_rmdir_paths.add(os.path.normcase(test_layout_target_gems_realpath))
|
|
os.rmdir = _mock_os_rmdir
|
|
|
|
test_game_asset_folder = os.path.join(cache_game_folder_realpath, test_asset_type)
|
|
cache_game_folder_gems_realpath = os.path.join(cache_game_folder_realpath, test_asset_type, 'gems')
|
|
cache_game_folder_game_realpath = os.path.join(cache_game_folder_realpath, test_asset_type)
|
|
mock_layout_tool_create_link_validation[os.path.normcase(cache_game_folder_gems_realpath)] = os.path.normcase(test_layout_target_gems_realpath)
|
|
mock_layout_tool_create_link_validation[os.path.normcase(cache_game_folder_game_realpath)] = os.path.normcase(test_layout_target_game_realpath)
|
|
|
|
else:
|
|
assert False, "Invalid Mode {}".format(mode)
|
|
os.makedirs(test_game_asset_folder, exist_ok=True)
|
|
|
|
def _mock_copy_asset_files_to_layout(project_path, project_asset_folder, target_platform, layout_target):
|
|
assert os.path.normcase(project_asset_folder) == os.path.normcase(test_game_asset_folder)
|
|
assert target_platform == test_target_platform
|
|
assert layout_target == test_layout_target_realpath
|
|
layout_tool.copy_asset_files_to_layout = _mock_copy_asset_files_to_layout
|
|
|
|
def _mock_layout_tool_create_link(src, dst, copy):
|
|
check_src = os.path.normcase(src)
|
|
check_dst = os.path.normcase(dst)
|
|
assert check_src in mock_layout_tool_create_link_validation, "Unexpected create link call to {}->{}".format(src, dst)
|
|
assert mock_layout_tool_create_link_validation[check_src] == check_dst, "Assertion on create linked failed: {}->{}".format(src, dst)
|
|
layout_tool.create_link = _mock_layout_tool_create_link
|
|
|
|
layout_tool.sync_layout_non_vfs(mode = mode,
|
|
target_platform = test_target_platform,
|
|
project_path = test_project_path,
|
|
asset_type = test_asset_type,
|
|
warning_on_missing_assets = False,
|
|
layout_target = test_layout_target_realpath,
|
|
override_pak_folder = test_override_pak_folder,
|
|
copy = False)
|
|
|
|
assert actual_rmdir_paths == expected_rmdir_paths
|
|
|
|
pass
|
|
finally:
|
|
os.rmdir = old_rmdir
|
|
layout_tool.copy_asset_files_to_layout = old_copy_asset_files_to_layout
|
|
layout_tool.remove_link = old_remove_link
|