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.
o3de/Code/Editor/Lib/Tests/test_EditorPythonBindings.cpp

308 lines
15 KiB
C++

/*
* 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
*
*/
#include "EditorDefs.h"
#include <AzTest/AzTest.h>
#include <Util/EditorUtils.h>
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <PythonEditorFuncs.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <Mocks/ICVarMock.h>
#include <Mocks/ISystemMock.h>
#include <Mocks/IConsoleMock.h>
#include <Mocks/IConsoleMock.h>
#include "IEditorMock.h"
namespace EditorPythonBindingsUnitTests
{
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::Invoke;
using ::testing::A;
using ::testing::_;
struct MockEditor final
{
~MockEditor()
{
SetIEditor(nullptr);
}
template <typename T>
void PrepareSetCVar(int cvarType, AZStd::function<void(const T)> func)
{
m_cvarType = cvarType;
ON_CALL(m_cvarMock, GetType()).WillByDefault(Return(m_cvarType));
ON_CALL(m_cvarMock, Set(A<T>())).WillByDefault(Invoke(func));
ON_CALL(m_console, GetCVar(_)).WillByDefault(Return(&m_cvarMock));
ON_CALL(m_system, GetIConsole()).WillByDefault(Return(&m_console));
ON_CALL(m_editorMock, GetSystem()).WillByDefault(Return(&m_system));
SetIEditor(&m_editorMock);
}
void PrepareGetCVarString(const char* value)
{
ON_CALL(m_cvarMock, GetString()).WillByDefault(Return(value));
ON_CALL(m_console, GetCVar(_)).WillByDefault(Return(&m_cvarMock));
ON_CALL(m_system, GetIConsole()).WillByDefault(Return(&m_console));
ON_CALL(m_editorMock, GetSystem()).WillByDefault(Return(&m_system));
SetIEditor(&m_editorMock);
}
void PrepareGetIConsole()
{
ON_CALL(m_system, GetIConsole()).WillByDefault(Return(&m_console));
ON_CALL(m_editorMock, GetSystem()).WillByDefault(Return(&m_system));
SetIEditor(&m_editorMock);
}
int m_cvarType = CVAR_INT;
::testing::NiceMock<CEditorMock> m_editorMock;
::testing::NiceMock<ConsoleMock> m_console;
::testing::NiceMock<SystemMock> m_system;
::testing::NiceMock<CVarMock> m_cvarMock;
};
class EditorPythonBindingsFixture
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_app.RegisterComponentDescriptor(AzToolsFramework::PythonEditorFuncsHandler::CreateDescriptor());
// Disable saving global user settings to prevent failure due to detecting file updates
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
void TearDown() override
{
m_app.Stop();
}
using TestEditorUtilityCommandFunc = AZStd::function<void(AZ::BehaviorContext* context)>;
void RunEditorUtilityCommandTest(TestEditorUtilityCommandFunc func)
{
AZ::BehaviorContext* behaviorContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationBus::Events::GetBehaviorContext);
ASSERT_TRUE(behaviorContext);
func(behaviorContext);
}
};
TEST_F(EditorPythonBindingsFixture, EditorUtilityCommands_ApiExists)
{
AZ::BehaviorContext* behaviorContext = m_app.GetBehaviorContext();
ASSERT_TRUE(behaviorContext);
EXPECT_TRUE(behaviorContext->m_methods.find("get_cvar") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("set_cvar_string") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("set_cvar_integer") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("set_cvar_float") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("run_console") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("enter_game_mode") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("is_in_game_mode") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("exit_game_mode") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("run_file") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("run_file_parameters") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("execute_command") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("message_box") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("message_box_yes_no") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("message_box_ok") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("edit_box") != behaviorContext->m_methods.end());
// Blocked by LY-101816
//EXPECT_TRUE(behaviorContext->m_methods.find("edit_box_check_data_type") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("open_file_box") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("get_axis_constraint") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("set_axis_constraint") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("get_pak_from_file") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("log") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("undo") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("redo") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("draw_label") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("combo_box") != behaviorContext->m_methods.end());
EXPECT_TRUE(behaviorContext->m_methods.find("crash") != behaviorContext->m_methods.end());
}
TEST_F(EditorPythonBindingsFixture, EditorPythonBindingsComponent_ApiExists)
{
AZ::BehaviorContext* behaviorContext = m_app.GetBehaviorContext();
ASSERT_TRUE(behaviorContext);
auto itPythonEditorBus = behaviorContext->m_ebuses.find("PythonEditorBus");
if (itPythonEditorBus != behaviorContext->m_ebuses.end())
{
AZ::BehaviorEBus* behaviorBus = itPythonEditorBus->second;
EXPECT_TRUE(behaviorBus->m_events.find("GetCVar") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetCVar") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetCVarFromString") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetCVarFromInteger") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetCVarFromFloat") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("PyRunConsole") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("EnterGameMode") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("IsInGameMode") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("ExitGameMode") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("EnterSimulationMode") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("IsInSimulationMode") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("ExitSimulationMode") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("RunLua") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("RunFile") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("RunFileParameters") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("ExecuteCommand") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("MessageBoxOkCancel") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("MessageBoxYesNo") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("MessageBoxOk") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("EditBox") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("EditBoxCheckDataType") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("OpenFileBox") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("GetAxisConstraint") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetAxisConstraint") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("GetPakFromFile") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("Log") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("Undo") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("Redo") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("DrawLabel") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("ComboBox") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetHidemaskAll") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetHidemaskNone") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetHidemaskInvert") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("SetHidemask") != behaviorBus->m_events.end());
EXPECT_TRUE(behaviorBus->m_events.find("GetHidemask") != behaviorBus->m_events.end());
}
}
TEST_F(EditorPythonBindingsFixture, EditorUtilityCommands_set_cvar_integer)
{
RunEditorUtilityCommandTest([](AZ::BehaviorContext* context)
{
int testInt = -1;
MockEditor mockEditor;
mockEditor.PrepareSetCVar<int>(CVAR_INT, [&testInt](const int value) { testInt = value; });
const char* testCvar = "test.cvar.int";
int intArg = 1;
AZStd::array<AZ::BehaviorValueParameter, 2> args;
args[0].Set(&testCvar);
args[1].Set(&intArg);
context->m_methods.find("set_cvar_integer")->second->Call(args.begin(), static_cast<unsigned int>(args.size()));
EXPECT_EQ(1, testInt);
});
}
TEST_F(EditorPythonBindingsFixture, EditorUtilityCommands_set_cvar_float)
{
RunEditorUtilityCommandTest([](AZ::BehaviorContext* context)
{
float testFloat = 0.0f;
MockEditor mockEditor;
mockEditor.PrepareSetCVar<float>(CVAR_FLOAT, [&testFloat](const float value) { testFloat = value; });
const char* testCvar = "test.cvar.float";
float input = 1.234f;
AZStd::array<AZ::BehaviorValueParameter, 2> args;
args[0].Set(&testCvar);
args[1].Set(&input);
context->m_methods.find("set_cvar_float")->second->Call(args.begin(), static_cast<unsigned int>(args.size()));
EXPECT_FLOAT_EQ(1.234f, testFloat);
});
}
TEST_F(EditorPythonBindingsFixture, EditorUtilityCommands_set_cvar_string)
{
RunEditorUtilityCommandTest([](AZ::BehaviorContext* context)
{
AZStd::string testString;
MockEditor mockEditor;
mockEditor.PrepareSetCVar<const char*>(CVAR_STRING, [&testString](const char* value) { testString = value; });
const char* testCvar = "test.cvar.string";
const char* input = "testvalue";
AZStd::array<AZ::BehaviorValueParameter, 2> args;
args[0].Set(&testCvar);
args[1].Set(&input);
context->m_methods.find("set_cvar_string")->second->Call(args.begin(), static_cast<unsigned int>(args.size()));
EXPECT_STREQ("testvalue", testString.c_str());
});
}
TEST_F(EditorPythonBindingsFixture, EditorUtilityCommands_get_cvar)
{
RunEditorUtilityCommandTest([](AZ::BehaviorContext* context)
{
MockEditor mockEditor;
mockEditor.PrepareGetCVarString("atestvalue");
const char* testCvar = "test.cvar.string";
AZStd::array<AZ::BehaviorValueParameter, 1> args;
args[0].Set(&testCvar);
const char* data;
AZ::BehaviorObject obj;
obj.m_typeId = azrtti_typeid<const char*>();
obj.m_address = &data;
AZ::BehaviorValueParameter result;
result.Set(&obj);
context->m_methods.find("get_cvar")->second->Call(args.begin(), static_cast<unsigned int>(args.size()), &result);
EXPECT_STREQ("atestvalue", reinterpret_cast<const char*>(obj.m_address));
});
}
TEST_F(EditorPythonBindingsFixture, EditorUtilityCommands_run_console)
{
RunEditorUtilityCommandTest([](AZ::BehaviorContext* context)
{
AZStd::string data;
MockEditor mockEditor;
mockEditor.PrepareGetIConsole();
ON_CALL(mockEditor.m_console, ExecuteString(_,_,_)).WillByDefault(Invoke([&data](const char* command, const bool, const bool) { data = command; }));
const char* testCvar = "enable_feature game.sim";
AZStd::array<AZ::BehaviorValueParameter, 1> args;
args[0].Set(&testCvar);
context->m_methods.find("run_console")->second->Call(args.begin(), static_cast<unsigned int>(args.size()));
EXPECT_STREQ(testCvar, data.c_str());
});
}
}