123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- #
- # 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
|