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.
328 lines
16 KiB
Python
328 lines
16 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
|
|
|
|
Unit Tests for android launcher-wrappers: all are sanity code-path tests, since no interprocess actions should be taken
|
|
"""
|
|
|
|
import unittest.mock as mock
|
|
|
|
import pytest
|
|
|
|
import ly_test_tools.launchers
|
|
import ly_test_tools.launchers.platforms.android.launcher
|
|
import ly_test_tools.launchers.exceptions
|
|
import ly_test_tools.mobile.android
|
|
|
|
pytestmark = pytest.mark.SUITE_smoke
|
|
|
|
VALID_ANDROID_CONFIG = {
|
|
'android': {
|
|
'id': '000000000000000'
|
|
}
|
|
}
|
|
PACKAGE_NAME = "dummy_project_path"
|
|
|
|
|
|
class MockedWorkspace(object):
|
|
|
|
def __init__(self):
|
|
self.paths = mock.MagicMock()
|
|
self.setup_assistant = mock.MagicMock()
|
|
self.asset_processor = mock.MagicMock()
|
|
self.shader_compiler = mock.MagicMock()
|
|
self.settings = mock.MagicMock()
|
|
|
|
self.paths.engine_root.return_value = 'engine_path'
|
|
self.paths.build_directory.return_value = 'build_directory'
|
|
self.paths.autoexec_file.return_value = 'autoexec.cfg'
|
|
|
|
self.project = 'project_name'
|
|
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.open', mock.mock_open())
|
|
class TestLauncherModule:
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.json.loads')
|
|
def test_GetPackageName_MockJsonHasKey_ReturnsValue(self, mock_json):
|
|
dummy_name = "some_name"
|
|
mock_json.return_value = {'android_settings': {'package_name': dummy_name}}
|
|
|
|
under_test = ly_test_tools.launchers.platforms.android.launcher.get_package_name(PACKAGE_NAME)
|
|
|
|
assert under_test == "some_name"
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.json.loads')
|
|
def test_GetPackageName_MockJsonMissingKey_SetupError(self, mock_json):
|
|
mock_json.return_value = {"one": "two"}
|
|
|
|
with pytest.raises(ly_test_tools.launchers.exceptions.SetupError):
|
|
ly_test_tools.launchers.platforms.android.launcher.get_package_name(PACKAGE_NAME)
|
|
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
def test_GetPid_EarlyVersionAndExplodes_NoPid(self, mock_output):
|
|
mock_output.side_effect = ['23', Exception]
|
|
|
|
under_test = ly_test_tools.launchers.platforms.android.launcher.get_pid("dummy", ["dummy"])
|
|
|
|
assert mock_output.call_count == 2
|
|
assert not under_test
|
|
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
def test_GetPid_LaterVersionAndExplodes_NoPid(self, mock_output):
|
|
mock_output.side_effect = ['25', Exception]
|
|
|
|
under_test = ly_test_tools.launchers.platforms.android.launcher.get_pid("dummy", ["dummy"])
|
|
|
|
assert mock_output.call_count == 2
|
|
assert not under_test
|
|
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
def test_GetPid_PidHasSingleValue_ReturnPid(self, mock_output):
|
|
mock_output.side_effect = ['25', '12']
|
|
|
|
under_test = ly_test_tools.launchers.platforms.android.launcher.get_pid("dummy", ["dummy"])
|
|
|
|
assert mock_output.call_count == 2
|
|
assert under_test == '12'
|
|
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
def test_GetPid_PidHasMultipleValues_ReturnPid(self, mock_output):
|
|
mock_output.side_effect = ['25', 'value 12 found']
|
|
|
|
under_test = ly_test_tools.launchers.platforms.android.launcher.get_pid("dummy", ["dummy"])
|
|
|
|
assert mock_output.call_count == 2
|
|
assert under_test == '12'
|
|
|
|
def test_GenerateMapCommand_HasMapArg_ReturnsMapCommand(self):
|
|
args_list = ['random_arg', '+map', 'map_name', 'another_arg']
|
|
|
|
under_test = ly_test_tools.launchers.platforms.android.launcher.generate_android_map_command(args_list)
|
|
|
|
assert under_test == 'map map_name'
|
|
|
|
def test_GenerateMapCommand_NoMapArg_ReturnsEmptyString(self):
|
|
args_list = ['random_arg', 'stuff', 'map_name', 'another_arg']
|
|
|
|
under_test = ly_test_tools.launchers.platforms.android.launcher.generate_android_map_command(args_list)
|
|
|
|
assert under_test == ''
|
|
|
|
|
|
@mock.patch('os.listdir', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.get_package_name',
|
|
mock.MagicMock(return_value=PACKAGE_NAME))
|
|
@mock.patch('ly_test_tools.environment.file_system.create_backup', mock.MagicMock)
|
|
@mock.patch('ly_test_tools.environment.file_system.restore_backup', mock.MagicMock)
|
|
@mock.patch('ly_test_tools.mobile.android.check_adb_connection_state', mock.MagicMock)
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.open', mock.mock_open())
|
|
class TestAndroidLauncher:
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
def test_ReadDeviceConfigINI_ReturnDeviceID_DeviceIDSet(self, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
|
|
assert launcher._device_id == VALID_ANDROID_CONFIG['android']['id']
|
|
assert ['adb', '-s', launcher._device_id] == launcher._adb_prefix_command
|
|
mock_config.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
def test_ReadDeviceConfigINI_ReturnsInvalidConfig_SetupError(self, mock_config):
|
|
mock_config.return_value = {'device': {'id': 12345}}
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
with pytest.raises(ly_test_tools.launchers.exceptions.SetupError):
|
|
ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.mobile.android.forward_tcp')
|
|
@mock.patch('ly_test_tools.mobile.android.reverse_tcp')
|
|
@mock.patch('ly_test_tools.mobile.android.undo_tcp_port_changes')
|
|
def test_EnableAndroidCaps_SetsAndroidCaps_CallsReverseTCPForwardTCP(self, mock_undo_tcp, mock_reverse_tcp,
|
|
mock_forward_tcp, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
under_test = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
under_test._enable_android_capabilities()
|
|
|
|
assert mock_reverse_tcp.call_count == 2
|
|
mock_forward_tcp.assert_called_once()
|
|
mock_undo_tcp.assert_called_with(VALID_ANDROID_CONFIG['android']['id'])
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher.backup_settings')
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher.configure_settings')
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher._enable_android_capabilities')
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher._is_valid_android_environment')
|
|
@mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock(return_value=True))
|
|
def test_Setup_ValidSetup_SetupCallsSucceed(self, mock_valid_env, mock_enable_caps, mock_config,
|
|
mock_configure_settings, mock_backup):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
mock_project_log_path = 'c:/mock_project/log/'
|
|
mock_workspace.paths.project_log.return_value = mock_project_log_path
|
|
|
|
under_test = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
under_test.setup()
|
|
|
|
mock_enable_caps.assert_called_once()
|
|
mock_valid_env.assert_called_once()
|
|
mock_backup.assert_called_once()
|
|
mock_configure_settings.assert_called_once()
|
|
mock_workspace.shader_compiler.start.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.mobile.android.can_run_android')
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher.backup_settings', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher.configure_settings',
|
|
mock.MagicMock())
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher._enable_android_capabilities',
|
|
mock.MagicMock())
|
|
@mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock(return_value=True))
|
|
def test_Setup_InvalidSetupNoADB_RaisesNotImplementedErrorException(self, mock_config, mock_can_run_android):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
mock_can_run_android.return_value = False
|
|
|
|
under_test = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
under_test.setup()
|
|
|
|
@mock.patch('ly_test_tools.mobile.android.get_devices')
|
|
@mock.patch('ly_test_tools.mobile.android.can_run_android', mock.MagicMock(return_value=True))
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher.backup_settings', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher.configure_settings',
|
|
mock.MagicMock())
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher._enable_android_capabilities',
|
|
mock.MagicMock())
|
|
@mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock(return_value=True))
|
|
def test_Setup_InvalidSetupNoDeviceConnected_RaisesSetupErrorException(self, mock_config, mock_get_devices):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
mock_get_devices.return_value = []
|
|
|
|
under_test = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
|
|
with pytest.raises(ly_test_tools.launchers.exceptions.SetupError):
|
|
under_test.setup()
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher.restore_settings')
|
|
@mock.patch('ly_test_tools.mobile.android.undo_tcp_port_changes', mock.MagicMock)
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.AndroidLauncher._enable_android_capabilities',
|
|
mock.MagicMock)
|
|
def test_Teardown_ValidTeardown_TeardownSucceeds(self, mock_restore, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
launcher.teardown()
|
|
|
|
mock_restore.assert_called_once()
|
|
mock_workspace.shader_compiler.stop.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.o3de.settings.LySettings.modify_platform_setting', mock.MagicMock)
|
|
def test_ConfigureSettings_DefaultValues_SetsValues(self, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
launcher.configure_settings()
|
|
|
|
assert mock_workspace.settings.modify_platform_setting.call_count == 1
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
@mock.patch('os.path.isfile', mock.MagicMock(return_value=False))
|
|
def test_Launch_HappyPathNoAutoexec_CallsLaunchCmd(self, mock_call, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
launcher.launch()
|
|
|
|
mock_call.assert_called_once_with([
|
|
'adb', '-s', VALID_ANDROID_CONFIG['android']['id'], 'shell', 'monkey', '-p', PACKAGE_NAME,
|
|
'-c', 'android.intent.category.LAUNCHER', '1'])
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
@mock.patch('ly_test_tools.mobile.android.push_files_to_device', mock.MagicMock)
|
|
@mock.patch('os.path.isfile', mock.MagicMock(return_value=True))
|
|
def test_Launch_HappyPathHasAutoExec_PushesFilesToDevice(self, mock_push_files, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
launcher.launch()
|
|
|
|
mock_push_files.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
@mock.patch('os.path.isfile', mock.MagicMock(return_value=False))
|
|
def test_Launch_MonkeyAbortedInLaunchResult_RaisesSetupError(self, mock_call, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_call.return_value = 'Monkey Aborted'
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
with pytest.raises(ly_test_tools.launchers.exceptions.SetupError):
|
|
launcher.launch()
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.get_pid')
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
def test_IsAlive_MockPidDNE_False(self, mock_config, mock_pid):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
mock_pid.return_value = ""
|
|
|
|
assert not launcher.is_alive()
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.android.launcher.get_pid')
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
def test_IsAlive_MockPidExists_True(self, mock_config, mock_pid):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
mock_pid.return_value = "1234"
|
|
|
|
assert launcher.is_alive()
|
|
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_call')
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
@mock.patch('ly_test_tools.mobile.android.check_adb_connection_state',
|
|
mock.MagicMock(return_value=ly_test_tools.mobile.android.SINGLE_DEVICE))
|
|
def test_Kill_HappyPath_KillCommandSuccess(self, mock_config, mock_call):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
|
|
# This is a direct call to a protected method, but the point of the test is to ensure functionality of this
|
|
# protected method, so we will allow this exception
|
|
launcher._kill()
|
|
|
|
mock_call.assert_called_once_with(
|
|
['adb', '-s', VALID_ANDROID_CONFIG['android']['id'], 'shell', 'am', 'force-stop', PACKAGE_NAME])
|
|
|
|
@mock.patch('ly_test_tools.launchers.platforms.base.Launcher._config_ini_to_dict')
|
|
def test_BinaryPath_Called_RaisesNotImplementedError(self, mock_config):
|
|
mock_config.return_value = VALID_ANDROID_CONFIG
|
|
mock_workspace = MockedWorkspace()
|
|
|
|
launcher = ly_test_tools.launchers.AndroidLauncher(mock_workspace, ["dummy"])
|
|
with pytest.raises(NotImplementedError):
|
|
launcher.binary_path()
|