""" All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or its licensors. For complete copyright and license terms please see the LICENSE at the root of this distribution (the "License"). All use of this software is governed by the License, or, if provided, by the license below or the license accompanying this file. Do not remove or modify any license notices. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 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 == 8 @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"]) 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()