diff --git a/Tools/LyTestTools/ly_test_tools/__init__.py b/Tools/LyTestTools/ly_test_tools/__init__.py index 1c78176b87..d534f5e0d2 100755 --- a/Tools/LyTestTools/ly_test_tools/__init__.py +++ b/Tools/LyTestTools/ly_test_tools/__init__.py @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__) # Supported platforms. ALL_PLATFORM_OPTIONS = ['android', 'ios', 'linux', 'mac', 'windows'] -ALL_LAUNCHER_OPTIONS = ['android', 'base', 'mac', 'windows', 'windows_editor', 'windows_dedicated'] +ALL_LAUNCHER_OPTIONS = ['android', 'base', 'mac', 'windows', 'windows_editor', 'windows_dedicated', 'windows_generic'] ANDROID = False IOS = False # Not implemented - see SPEC-2505 LINUX = sys.platform.startswith('linux') # Not implemented - see SPEC-2501 @@ -37,12 +37,15 @@ if WINDOWS: HOST_OS_PLATFORM = 'windows' HOST_OS_EDITOR = 'windows_editor' HOST_OS_DEDICATED_SERVER = 'windows_dedicated' + HOST_OS_GENERIC_EXECUTABLE = 'windows_generic' import ly_test_tools.mobile.android - from ly_test_tools.launchers import AndroidLauncher, WinLauncher, DedicatedWinLauncher, WinEditor + from ly_test_tools.launchers import ( + AndroidLauncher, WinLauncher, DedicatedWinLauncher, WinEditor, WinGenericLauncher) ANDROID = ly_test_tools.mobile.android.can_run_android() LAUNCHERS['windows'] = WinLauncher LAUNCHERS['windows_editor'] = WinEditor LAUNCHERS['windows_dedicated'] = DedicatedWinLauncher + LAUNCHERS['windows_generic'] = WinGenericLauncher LAUNCHERS['android'] = AndroidLauncher elif MAC: HOST_OS_PLATFORM = 'mac' diff --git a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py index e09b1ab54c..87a396ea1c 100755 --- a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py +++ b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py @@ -29,7 +29,7 @@ import ly_test_tools.environment.file_system import ly_test_tools.launchers.launcher_helper import ly_test_tools.launchers.platforms.base import ly_test_tools.environment.watchdog -from ly_test_tools import ALL_PLATFORM_OPTIONS, HOST_OS_PLATFORM, HOST_OS_DEDICATED_SERVER +from ly_test_tools import ALL_PLATFORM_OPTIONS, HOST_OS_PLATFORM, HOST_OS_DEDICATED_SERVER, HOST_OS_GENERIC_EXECUTABLE logger = logging.getLogger(__name__) @@ -281,6 +281,21 @@ def _dedicated_launcher(request, workspace, launcher_platform, level=""): return launcher + +@pytest.fixture(scope="function") +def generic_launcher(workspace, request, crash_log_watchdog): + # type: (...) -> ly_test_tools.launchers.platforms.base.Launcher + return _generic_launcher( + workspace=workspace, + launcher_platform=get_fixture_argument(request, 'launcher_platform', HOST_OS_GENERIC_EXECUTABLE), + exe_file_name=get_fixture_argument(request, 'exe_file_name', '')) + + +def _generic_launcher(workspace, launcher_platform, exe_file_name): + """Separate implementation to call directly during unit tests""" + return ly_test_tools.launchers.launcher_helper.create_generic_launcher(workspace, launcher_platform, exe_file_name) + + @pytest.fixture def automatic_process_killer(request): # type: (_pytest.fixtures.SubRequest) -> ly_process_killer diff --git a/Tools/LyTestTools/ly_test_tools/launchers/__init__.py b/Tools/LyTestTools/ly_test_tools/launchers/__init__.py index 2203d2eb39..5f7a391afb 100755 --- a/Tools/LyTestTools/ly_test_tools/launchers/__init__.py +++ b/Tools/LyTestTools/ly_test_tools/launchers/__init__.py @@ -11,5 +11,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. from ly_test_tools.launchers.platforms.base import Launcher from ly_test_tools.launchers.platforms.mac.launcher import MacLauncher -from ly_test_tools.launchers.platforms.win.launcher import WinLauncher, DedicatedWinLauncher, WinEditor +from ly_test_tools.launchers.platforms.win.launcher import ( + WinLauncher, DedicatedWinLauncher, WinEditor, WinGenericLauncher) from ly_test_tools.launchers.platforms.android.launcher import AndroidLauncher diff --git a/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py b/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py index 4ed4c43dee..45f5e6e73f 100755 --- a/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py +++ b/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py @@ -61,3 +61,19 @@ def create_editor(workspace, launcher_platform=ly_test_tools.HOST_OS_EDITOR, arg """ launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform, ly_test_tools.HOST_OS_EDITOR) return launcher_class(workspace, args) + + +def create_generic_launcher(workspace, launcher_platform, exe_file_name, args=None): + # type: (ly_test_tools.managers.workspace.WorkspaceManager, str, str, List[str]) -> Launcher + """ + Create a generic launcher compatible with the specified workspace. + Allows custom .exe files to serve as the launcher instead of ones listed in the ly_test_tools.LAUNCHERS constant + + :param workspace: lumberyard workspace to use + :param launcher_platform: the platform to target for a launcher (i.e. 'windows' for WinLauncher) + :param exe_file_name: .exe file name which has to be launched for this launcher (i.e. 'MaterialEditor.exe') + :param args: List of arguments to pass to the launcher's 'args' argument during construction + :return: Launcher instance. + """ + launcher_class = ly_test_tools.LAUNCHERS.get(launcher_platform, ly_test_tools.HOST_OS_GENERIC_EXECUTABLE) + return launcher_class(workspace, exe_file_name, args) diff --git a/Tools/LyTestTools/ly_test_tools/launchers/platforms/win/launcher.py b/Tools/LyTestTools/ly_test_tools/launchers/platforms/win/launcher.py index 138d788b9f..30c3e6eddd 100755 --- a/Tools/LyTestTools/ly_test_tools/launchers/platforms/win/launcher.py +++ b/Tools/LyTestTools/ly_test_tools/launchers/platforms/win/launcher.py @@ -223,3 +223,28 @@ class WinEditor(WinLauncher): """ assert self.workspace.project is not None return os.path.join(self.workspace.paths.build_directory(), "Editor.exe") + + +class WinGenericLauncher(WinLauncher): + + def __init__(self, build, exe_file_name, args=None): + super(WinGenericLauncher, self).__init__(build, args) + self.exe_file_name = exe_file_name + self.expected_executable_path = os.path.join( + self.workspace.paths.build_directory(), f"{self.exe_file_name}.exe") + + if not os.path.exists(self.expected_executable_path): + raise ProcessNotStartedError( + f"Unable to locate executable '{self.exe_file_name}.exe' " + f"in path: '{self.expected_executable_path}'") + + def binary_path(self): + """ + Return full path to the .exe file for this build's configuration and project + Relies on the build_directory() in self.workspace.paths to be accurate + + :return: full path to the given exe file + """ + assert self.workspace.project is not None, ( + 'Project cannot be NoneType - please specify a project name string.') + return self.expected_executable_path diff --git a/Tools/LyTestTools/tests/unit/test_launcher_win.py b/Tools/LyTestTools/tests/unit/test_launcher_win.py index 193589f457..37e04ac99e 100755 --- a/Tools/LyTestTools/tests/unit/test_launcher_win.py +++ b/Tools/LyTestTools/tests/unit/test_launcher_win.py @@ -184,3 +184,31 @@ class TestDedicatedWinLauncher(object): launcher.workspace.build(dedicated=True) mock_workspace.build.assert_called_once_with(dedicated=True) + + +class TestWinGenericLauncher(object): + + @mock.patch('ly_test_tools.launchers.platforms.win.launcher.os.path.exists') + def test_BinaryPath_DummyPathExeExists_AddPathToExe(self, mock_os_path_exists): + dummy_path = "dummy_workspace_path" + dummy_executable = 'SomeCustomLauncher.exe' + mock_os_path_exists.return_value = True + mock_workspace = mock.MagicMock() + mock_workspace.paths.build_directory.return_value = dummy_path + launcher = ly_test_tools.launchers.WinGenericLauncher(mock_workspace, dummy_executable, ["some_args"]) + + under_test = launcher.binary_path() + + assert dummy_executable in under_test, f"executable named {dummy_executable} not found" + assert dummy_path in under_test, "workspace path unexpectedly missing " + + @mock.patch('ly_test_tools.launchers.platforms.win.launcher.os.path.exists') + def test_BinaryPath_DummyPathExeDoesNotExist_RaiseProcessNotStartedError(self, mock_os_path_exists): + dummy_path = "dummy_workspace_path" + dummy_executable = 'SomeCustomLauncher.exe' + mock_os_path_exists.return_value = False + mock_workspace = mock.MagicMock() + mock_workspace.paths.build_directory.return_value = dummy_path + + with pytest.raises(ly_test_tools.launchers.exceptions.ProcessNotStartedError): + ly_test_tools.launchers.WinGenericLauncher(mock_workspace, dummy_executable, ["some_args"])