Initial update for this module's unit tests on Linux

Signed-off-by: sweeneys <sweeneys@amazon.com>
This commit is contained in:
sweeneys
2021-09-22 17:50:12 -07:00
parent 92c4b22445
commit 09ce73aa44
14 changed files with 417 additions and 76 deletions
+10 -7
View File
@@ -11,16 +11,16 @@ import sys
logger = logging.getLogger(__name__)
# Supported platforms.
# Supported platforms
ALL_PLATFORM_OPTIONS = ['android', 'ios', 'linux', 'mac', 'windows']
ALL_LAUNCHER_OPTIONS = ['android', 'base', 'mac', 'windows', 'windows_editor', 'windows_dedicated', 'windows_generic']
ALL_LAUNCHER_OPTIONS = ['android', 'base', 'linux', '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
MAC = sys.platform.startswith('darwin')
WINDOWS = sys.platform.startswith('win')
# Detect platforms.
# Detect available platforms
HOST_OS_PLATFORM = 'unknown'
HOST_OS_EDITOR = 'unknown'
HOST_OS_DEDICATED_SERVER = 'unknown'
@@ -51,9 +51,12 @@ elif MAC:
from ly_test_tools.launchers import MacLauncher
LAUNCHERS['mac'] = MacLauncher
elif LINUX:
logger.warning(f'Linux operating system is currently not supported, LyTestTools only supports Windows and Mac.')
HOST_OS_PLATFORM = 'linux'
HOST_OS_EDITOR = NotImplementedError('LyTestTools does not yet support Linux editor')
HOST_OS_DEDICATED_SERVER = NotImplementedError('LyTestTools does not yet support Linux dedicated server')
HOST_OS_EDITOR = 'linux_editor'
HOST_OS_DEDICATED_SERVER = 'linux_dedicated'
from ly_test_tools.launchers.platforms.linux.launcher import (LinuxLauncher, LinuxEditor, DedicatedLinuxLauncher)
LAUNCHERS['linux'] = LinuxLauncher
LAUNCHERS['linux_editor'] = LinuxEditor
LAUNCHERS['linux_dedicated'] = DedicatedLinuxLauncher
else:
logger.warning(f'WARNING: LyTestTools only supports Windows and Mac, got HOST_OS_PLATFORM: "{HOST_OS_PLATFORM}".')
logger.warning(f'WARNING: LyTestTools only supports Windows, Mac, and Linux. Unexpectedly detected HOST_OS_PLATFORM: "{HOST_OS_PLATFORM}".')
@@ -0,0 +1,78 @@
"""
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
Linux directory and workspace mappings
"""
import os
import logging
from ly_test_tools._internal.managers.workspace import AbstractWorkspaceManager
from ly_test_tools._internal.managers.abstract_resource_locator import AbstractResourceLocator
logger = logging.getLogger(__name__)
CACHE_DIR = 'linux'
CONFIG_FILE = 'system_linux_pc.cfg'
class _LinuxResourceManager(AbstractResourceLocator):
"""
Override for locating resources in a Linux operating system running LyTestTools.
"""
def __init__(self, build_directory: str, project: str):
pass
def platform_config_file(self):
"""
:return: path to the platform config file
"""
return os.path.join(self.engine_root(), CONFIG_FILE)
def platform_cache(self):
"""
:return: path to cache for the Linux operating system
"""
return os.path.join(self.project_cache(), CACHE_DIR)
def project_log(self):
"""
:return: path to 'log' dir in the platform cache dir
"""
return os.path.join(self.project(), 'user', 'log')
def project_screenshots(self):
"""
:return: path to 'screenshot' dir in the platform cache dir
"""
return os.path.join(self.project(), 'user', 'ScreenShots')
def editor_log(self):
"""
:return: path to editor.log
"""
return os.path.join(self.project_log(), "editor.log")
class LinuxWorkspaceManager(AbstractWorkspaceManager):
"""
A Mac host WorkspaceManager. Contains Mac overridden functions for the AbstractWorkspaceManager class.
Also creates a Mac host ResourceLocator for directory and build mappings.
"""
def __init__(
self,
build_directory=None,
project=None,
tmp_path=None,
output_path=None,
):
# Type: (str,str,str,str) -> None
super(LinuxWorkspaceManager, self).__init__(
_LinuxResourceManager(build_directory, project),
project=project,
tmp_path=tmp_path,
output_path=output_path,
)
@@ -9,7 +9,7 @@ Helper file for assisting in building workspaces and setting up LTT with the cur
import ly_test_tools._internal.pytest_plugin as pytest_plugin
import ly_test_tools._internal.managers.workspace as internal_workspace
from ly_test_tools import MAC, WINDOWS
from ly_test_tools import LINUX, MAC, WINDOWS
import os, stat
@@ -47,6 +47,11 @@ def create_builtin_workspace(
elif MAC:
from ly_test_tools._internal.managers.platforms.mac import MacWorkspaceManager
build_class = MacWorkspaceManager
elif LINUX:
from ly_test_tools._internal.managers.platforms.linux import LinuxWorkspaceManager
build_class = LinuxWorkspaceManager
else:
raise NotImplementedError("No workspace manager found for current Operating System")
instance = build_class(
build_directory=build_directory,
@@ -12,8 +12,8 @@ import psutil
import subprocess
import ctypes
import ly_test_tools
import ly_test_tools.environment.waiter as waiter
from ly_test_tools import WINDOWS, MAC
logger = logging.getLogger(__name__)
_PROCESS_OUTPUT_ENCODING = 'utf-8'
@@ -182,7 +182,7 @@ def process_is_unresponsive(name):
:param name: the name of the process to check
:return: True if the specified process is unresponsive and False otherwise
"""
if WINDOWS:
if ly_test_tools.WINDOWS:
output = check_output(['tasklist',
'/FI', f'IMAGENAME eq {name}',
'/FI', 'STATUS eq NOT RESPONDING'])
@@ -194,7 +194,7 @@ def process_is_unresponsive(name):
return True
logger.debug(f"Process '{name}' was not unresponsive.")
return False
elif MAC:
else:
cmd = ["ps", "-axc", "-o", "command,state"]
output = check_output(cmd)
for line in output.splitlines()[1:]:
@@ -209,8 +209,6 @@ def process_is_unresponsive(name):
return True
logger.debug(f"Process '{name}' was not unresponsive.")
return False
else:
raise NotImplementedError('Only Windows and Mac hosts are supported.')
def check_output(command, **kwargs):
@@ -406,7 +404,7 @@ def close_windows_process(pid, timeout=20, raise_on_missing=False):
:param pid: the pid of the process to kill
:param raise_on_missing: if set to True, raise RuntimeError if the process does not already exist
"""
if not WINDOWS:
if not ly_test_tools.WINDOWS:
raise NotImplementedError("close_windows_process() is only implemented on Windows.")
if pid is None:
@@ -8,8 +8,10 @@ Reg cleaner: tools for working with the lumberyard windows registry keys
"""
import logging
import os
import winreg
import ly_test_tools
if ly_test_tools.WINDOWS:
import winreg # OS-specific module availability, must be mocked if this file is accessed elsewhere e.g. unit tests
import ly_test_tools.environment.process_utils as process_utils
CONST_LY_REG = r'SOFTWARE\O3DE\O3DE'
@@ -14,8 +14,8 @@ import re
import psutil
import time
import ly_test_tools
import ly_test_tools.environment.process_utils as process_utils
from ly_test_tools import WINDOWS
logger = logging.getLogger(__name__)
@@ -28,7 +28,7 @@ class Watchdog(object):
DEFAULT_JOIN_TIMEOUT = 10 # seconds
def __init__(self, bool_fn, interval=1, raise_on_condition=True, name='ly_test_watchdog', error_message=''):
# type: (func, int, bool, str, str) -> Watchdog
# type: (function, int, bool, str, str) -> Watchdog
"""
A Watchdog object that takes in a boolean function. It spawns a thread that loops over the boolean function
until it returns True. If the boolean function returns True, then a flag will be set and an exception
@@ -44,7 +44,7 @@ class Watchdog(object):
"""
self.caught_failure = False
self.name = name
self._bool_fn = bool_fn
self._interval = interval
self._raise_on_condition = raise_on_condition
@@ -66,7 +66,7 @@ class Watchdog(object):
self.caught_failure = False
def stop(self, join_timeout=DEFAULT_JOIN_TIMEOUT):
# type: () -> None
# type: (int) -> None
"""
Stops the watchdog's thread if it's executing by enabling its shutdown Event. If the target function's condition
was found, then it will either raise an exception or log an error message.
@@ -106,7 +106,7 @@ class Watchdog(object):
"""
The main function of the watchdog thread. It will repeatedly call its target function until the target function
returns True, in which it will set the self.caught_failure attribute to True.
:return: None
"""
while True:
@@ -128,7 +128,7 @@ class ProcessUnresponsiveWatchdog(Watchdog):
Watches a process ID and reports if it is unresponsive for a given timeout. If multiple processes need to be
watched, then multiple watchdogs should be instantiated.
Note: This is for windows OS only.
:param process_id: The process id to watch
:param interval: The interval (in seconds) for how frequently the bool_fn is called on the thread.
:param raise_on_condition: If True, raises an exception when bool_fn returns True. If False, logs an error
@@ -138,8 +138,8 @@ class ProcessUnresponsiveWatchdog(Watchdog):
:param unresponsive_timeout_seconds: How long the process needs to be unresponsive for in order for the watchdog
to report (in seconds).
"""
if not WINDOWS:
raise (NotImplementedError, "Process watchdog is only implemented on Windows.")
if not ly_test_tools.WINDOWS:
pass # TODO add non-windows support
self._unresponsive_timeout = unresponsive_timeout_seconds
self._calculated_timeout_point = None
self._pid = process_id
@@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
"""
from ly_test_tools.launchers.platforms.base import Launcher
from ly_test_tools.launchers.platforms.linux.launcher import LinuxLauncher, LinuxEditor, DedicatedLinuxLauncher
from ly_test_tools.launchers.platforms.mac.launcher import MacLauncher
from ly_test_tools.launchers.platforms.win.launcher import (
WinLauncher, DedicatedWinLauncher, WinEditor, WinGenericLauncher)
from ly_test_tools.launchers.platforms.win.launcher import WinLauncher, DedicatedWinLauncher, WinEditor, WinGenericLauncher
from ly_test_tools.launchers.platforms.android.launcher import AndroidLauncher
@@ -0,0 +1,6 @@
"""
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
"""
@@ -0,0 +1,224 @@
"""
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
Linux compatible launcher
"""
import logging
import os
import subprocess
import ly_test_tools.environment.waiter
import ly_test_tools.launchers.exceptions
from ly_test_tools.launchers.platforms.base import Launcher
from ly_test_tools.launchers.exceptions import TeardownError, ProcessNotStartedError
from tempfile import TemporaryFile
log = logging.getLogger(__name__)
class LinuxLauncher(Launcher):
def __init__(self, build, args):
super(LinuxLauncher, self).__init__(build, args)
self._proc = None
self._ret_code = None
self._tmpout = None
log.debug("Initialized Linux Launcher")
def binary_path(self):
"""
Return full path to the launcher for this build's configuration and project
:return: full path to <project>.GameLauncher
"""
assert self.workspace.project is not None
return os.path.join(self.workspace.paths.build_directory(), f"{self.workspace.project}.GameLauncher")
def setup(self, backupFiles=True, launch_ap=True, configure_settings=True):
"""
Perform setup of this launcher, must be called before launching.
Subclasses should call its parent's setup() before calling its own code, unless it changes configuration files
:param backupFiles: Bool to backup setup files
:param lauch_ap: Bool to lauch the asset processor
:return: None
"""
# Backup
if backupFiles:
self.backup_settings()
# Base setup defaults to None
if launch_ap is None:
launch_ap = True
# Modify and re-configure
if configure_settings:
self.configure_settings()
super(LinuxLauncher, self).setup(backupFiles, launch_ap)
def launch(self):
"""
Launch the executable and track the subprocess
:return: None
"""
command = [self.binary_path()] + self.args
self._tmpout = TemporaryFile()
self._proc = subprocess.Popen(command, stdout=self._tmpout, stderr=self._tmpout, universal_newlines=True)
log.debug(f"Started Linux Launcher with command: {command}")
def get_output(self, encoding="utf-8"):
if self._tmpout is None:
raise ProcessNotStartedError("Process must be started before retrieving output")
self._tmpout.seek(0)
return self._tmpout.read().decode(encoding)
def teardown(self):
"""
Perform teardown of this launcher, undoing actions taken by calling setup()
Subclasses should call its parent's teardown() after performing its own teardown.
:return: None
"""
self.restore_settings()
super(LinuxLauncher, self).teardown()
def kill(self):
"""
This is a hard kill, and then wait to make sure until it actually ended.
:return: None
"""
if self._proc is not None:
self._proc.kill()
ly_test_tools.environment.waiter.wait_for(
lambda: not self.is_alive(),
exc=ly_test_tools.launchers.exceptions.TeardownError(
f"Unable to terminate active Linux Launcher with process ID {self._proc.pid}")
)
self._proc = None
self._ret_code = None
log.debug("Linux Launcher terminated successfully")
def is_alive(self):
"""
Check the process to verify activity. Side effect of setting self.proc to None if it has ended.
:return: None
"""
if self._proc is None:
return False
else:
if self._proc.poll() is not None:
self._ret_code = self._proc.poll()
self._proc = None
return False
return True
def get_pid(self):
# type: () -> int or None
"""
Returns the pid of the launcher process if it exists, else it returns None
:return: process id or None
"""
if self._proc:
return self._proc.pid
return None
def get_returncode(self):
# type: () -> int or None
"""
Returns the returncode of the launcher process if it exists, else return None.
The returncode attribute is set when the process is terminated.
:return: The returncode of the launcher's process
"""
if self._proc:
return self._proc.poll()
else:
return self._ret_code
def check_returncode(self):
# type: () -> None
"""
Checks the returncode of the launcher if it exists. Raises a CrashError if the returncode is non-zero. Returns
None otherwise. This function must be called after exiting the launcher properly and NOT using its provided
teardown(). Provided teardown() will always return a non-zero returncode and should not be checked.
:return: None
"""
return_code = self.get_returncode()
if return_code != 0:
log.error(f"Launcher exited with non-zero return code: {return_code}")
raise ly_test_tools.launchers.exceptions.CrashError()
return None
def configure_settings(self):
"""
Configures system level settings and syncs the launcher to the targeted console IP.
:return: None
"""
# Update settings via the settings registry to avoid modifying the bootstrap.cfg
host_ip = '127.0.0.1'
self.args.append(f'--regset="/Amazon/AzCore/Bootstrap/project_path={self.workspace.paths.project()}"')
self.args.append(f'--regset="/Amazon/AzCore/Bootstrap/remote_ip={host_ip}"')
self.args.append('--regset="/Amazon/AzCore/Bootstrap/wait_for_connect=1"')
self.args.append(f'--regset="/Amazon/AzCore/Bootstrap/allowed_list={host_ip}"')
self.workspace.settings.modify_platform_setting("r_AssetProcessorShaderCompiler", 1)
self.workspace.settings.modify_platform_setting("r_ShaderCompilerServer", host_ip)
self.workspace.settings.modify_platform_setting("log_RemoteConsoleAllowedAddresses", host_ip)
class DedicatedLinuxLauncher(LinuxLauncher):
def setup(self, backupFiles=True, launch_ap=False):
"""
Perform setup of this launcher, must be called before launching.
Subclasses should call its parent's setup() before calling its own code, unless it changes configuration files
:param backupFiles: Bool to backup setup files
:param lauch_ap: Bool to lauch the asset processor
:return: None
"""
# Base setup defaults to None
if launch_ap is None:
launch_ap = False
super(DedicatedLinuxLauncher, self).setup(backupFiles, launch_ap)
def binary_path(self):
"""
Return full path to the dedicated server launcher for the build directory.
:return: full path to <project>Launcher_Server
"""
assert self.workspace.project is not None, (
'Project cannot be NoneType - please specify a project name string.')
return os.path.join(f"{self.workspace.paths.build_directory()}",
f"{self.workspace.project}.ServerLauncher")
class LinuxEditor(LinuxLauncher):
def __init__(self, build, args):
super(LinuxEditor, self).__init__(build, args)
self.args.append('--regset="/Amazon/Settings/EnableSourceControl=false"')
self.args.append('--regset="/Amazon/AWS/Preferences/AWSAttributionConsentShown=true"')
self.args.append('--regset="/Amazon/AWS/Preferences/AWSAttributionEnabled=false"')
def binary_path(self):
"""
Return full path to the Editor for this build's configuration and project
:return: full path to Editor
"""
assert self.workspace.project is not None
return os.path.join(self.workspace.paths.build_directory(), "Editor")
@@ -8,7 +8,6 @@ Unit tests for ly_test_tools.builtin.helpers functions.
"""
import unittest.mock as mock
import os
import pytest
import ly_test_tools.builtin.helpers
@@ -112,10 +111,11 @@ class TestBuiltinHelpers(object):
ly_test_tools._internal.managers.abstract_resource_locator._find_engine_root(
initial_path='mock_dev_dir')
@mock.patch('ly_test_tools._internal.managers.workspace.AbstractWorkspaceManager.teardown')
@mock.patch('ly_test_tools._internal.managers.workspace.AbstractWorkspaceManager.setup')
@mock.patch('ly_test_tools._internal.managers.artifact_manager.NullArtifactManager', mock.MagicMock())
@mock.patch('os.path.exists', mock.MagicMock(return_value=True))
def test_SetupBuiltinWorkspace_ValidWorkspaceSetup_ReturnsWorkspaceObject(self, mock_setup):
def test_SetupTeardownBuiltinWorkspace_ValidWorkspaceSetup_ReturnsWorkspaceObject(self, mock_setup, mock_teardown):
mock_test_name = 'mock_test_name'
mock_test_amount = 10
mock_workspace = ly_test_tools.builtin.helpers.create_builtin_workspace(
@@ -125,25 +125,16 @@ class TestBuiltinHelpers(object):
output_path='mock_output_path',
)
under_test = ly_test_tools.builtin.helpers.setup_builtin_workspace(
setup_test = ly_test_tools.builtin.helpers.setup_builtin_workspace(
mock_workspace, mock_test_name, mock_test_amount)
assert under_test == mock_workspace
assert setup_test == mock_workspace
assert mock_setup.call_count == 1
mock_workspace.artifact_manager.set_test_name.assert_called_with(
test_name=mock_test_name, amount=mock_test_amount)
@mock.patch('ly_test_tools._internal.managers.workspace.AbstractWorkspaceManager.teardown')
@mock.patch('os.path.exists', mock.MagicMock(return_value=True))
def test_TeardownBuiltinWorkspace_ValidWorkspaceSetup_ReturnsWorkspaceObject(self, mock_teardown):
mock_workspace = ly_test_tools.builtin.helpers.create_builtin_workspace(
build_directory='build_directory',
project='mock_project',
tmp_path='mock_tmp_path',
output_path='mock_output_path',
)
# Teardown not tested separately due to patched MockedAbstractResourceLocator creating a StopIteration error on Linux
teardown_test = ly_test_tools.builtin.helpers.teardown_builtin_workspace(mock_workspace)
under_test = ly_test_tools.builtin.helpers.teardown_builtin_workspace(mock_workspace)
assert under_test == mock_workspace
assert teardown_test == mock_workspace
assert mock_teardown.call_count == 1
@@ -208,20 +208,6 @@ class TestUnZip(unittest.TestCase):
self.assertEqual(path, expected_path)
@mock.patch('os.path.exists')
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
@mock.patch('os.path.join')
def test_Unzip_ReleaseBuild_JoinCalledWithNoPathNoExtension(self, mock_join, mock_check_free, mock_exists):
path = ''
self.src_path = r'C:\packages\lumberyard-1.2.0.3-54321-pc-1234.zip'
mock_exists.return_value = self.exists
with mock.patch(self.decomp_obj_name, self.mock_decomp):
path = self.call_decomp(self.dest_path, self.src_path)
mock_join.assert_called_once_with(self.dest_path, 'lumberyard-1.2.0.3-54321-pc-1234')
@mock.patch('ly_test_tools.environment.file_system.logger')
@mock.patch('os.path.exists')
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
@@ -375,20 +361,6 @@ class TestUnTgz(unittest.TestCase):
self.assertEqual(path, expected_path)
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
@mock.patch('os.path.join')
@mock.patch('os.stat')
def test_Untgz_ReleaseBuild_JoinCalledWithNoPathNoExtension(self, mock_stat, mock_join, mock_check_free):
mock_stat.return_value = self.src_stat
path = ''
self.src_path = r'C:\packages\lumberyard-1.2.0.3-54321-pc-1234.zip'
with mock.patch(self.decomp_obj_name, self.mock_decomp):
path = self.call_decomp(self.dest_path, self.src_path)
mock_join.assert_called_once_with(self.dest_path, 'lumberyard-1.2.0.3-54321-pc-1234')
@mock.patch('ly_test_tools.environment.file_system.logger')
@mock.patch('os.path.exists')
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
@@ -204,21 +204,20 @@ class TestLauncherBuilder(object):
"""
def test_CreateLauncher_DummyWorkspace_DefaultLauncher(self):
dummy_workspace = mock.MagicMock()
launcher_platform = 'windows'
under_test = ly_test_tools.launchers.launcher_helper.create_launcher(
dummy_workspace, launcher_platform)
dummy_workspace, ly_test_tools.HOST_OS_EDITOR)
assert isinstance(under_test, ly_test_tools.launchers.Launcher)
def test_CreateDedicateLauncher_DummyWorkspace_DefaultLauncher(self):
dummy_workspace = mock.MagicMock()
launcher_platform = 'windows_dedicated'
under_test = ly_test_tools.launchers.launcher_helper.create_dedicated_launcher(
dummy_workspace, launcher_platform)
dummy_workspace, ly_test_tools.HOST_OS_DEDICATED_SERVER)
assert isinstance(under_test, ly_test_tools.launchers.Launcher)
@mock.patch('os.path.exists', mock.MagicMock(return_value=True))
def test_CreateEditor_DummyWorkspace_DefaultLauncher(self):
dummy_workspace = mock.MagicMock()
launcher_platform = 'windows_editor'
dummy_workspace.paths.build_directory.return_value = 'dummy'
under_test = ly_test_tools.launchers.launcher_helper.create_editor(
dummy_workspace, launcher_platform)
dummy_workspace, ly_test_tools.HOST_OS_GENERIC_EXECUTABLE)
assert isinstance(under_test, ly_test_tools.launchers.Launcher)
@@ -0,0 +1,63 @@
"""
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
Unit Tests for linux launcher-wrappers: all are sanity code-path tests, since no interprocess actions should be taken
"""
import os
import pytest
import unittest.mock as mock
import ly_test_tools.launchers
pytestmark = pytest.mark.SUITE_smoke
class TestLinuxLauncher(object):
def test_Construct_TestDoubles_LinuxLauncherCreated(self):
under_test = ly_test_tools.launchers.LinuxLauncher(mock.MagicMock(), ["some_args"])
assert isinstance(under_test, ly_test_tools.launchers.Launcher)
assert isinstance(under_test, ly_test_tools.launchers.LinuxLauncher)
def test_BinaryPath_DummyPath_AddPathToApp(self):
dummy_path = "dummy_workspace_path"
dummy_project = "dummy_project"
mock_workspace = mock.MagicMock()
mock_workspace.paths.build_directory.return_value = dummy_path
mock_workspace.project = dummy_project
launcher = ly_test_tools.launchers.LinuxLauncher(mock_workspace, ["some_args"])
under_test = launcher.binary_path()
expected = os.path.join(dummy_path, f"{dummy_project}.GameLauncher")
assert under_test == expected
@mock.patch('ly_test_tools.launchers.LinuxLauncher.binary_path', mock.MagicMock)
@mock.patch('subprocess.Popen')
def test_Launch_DummyArgs_ArgsPassedToPopen(self, mock_subprocess):
dummy_args = ["some_args"]
launcher = ly_test_tools.launchers.LinuxLauncher(mock.MagicMock(), dummy_args)
launcher.launch()
mock_subprocess.assert_called_once()
name, args, kwargs = mock_subprocess.mock_calls[0]
unpacked_args = args[0] # args is a list inside a tuple
assert len(dummy_args) > 0, "accidentally removed dummy_args"
for expected_arg in dummy_args:
assert expected_arg in unpacked_args
@mock.patch('ly_test_tools.launchers.LinuxLauncher.is_alive')
def test_Kill_MockAliveFalse_SilentSuccess(self, mock_alive):
mock_alive.return_value = False
mock_proc = mock.MagicMock()
launcher = ly_test_tools.launchers.LinuxLauncher(mock.MagicMock(), ["dummy"])
launcher._proc = mock_proc
launcher.kill()
mock_proc.kill.assert_called_once()
mock_alive.assert_called_once()
@@ -191,7 +191,7 @@ class TestSubprocessCheckCallWrapperSafe(unittest.TestCase):
reason="tests.unit.test_process_utils is restricted to the Windows platform.")
class TestCloseWindowsProcess(unittest.TestCase):
@mock.patch('ly_test_tools.environment.process_utils.WINDOWS', False)
@mock.patch('ly_test_tools.WINDOWS', False)
def test_CloseWindowsProccess_NotOnWindows_Error(self):
with pytest.raises(NotImplementedError):
process_utils.close_windows_process(1)