Integrating latest 47acbe8

This commit is contained in:
alexpete
2021-03-25 13:57:57 -07:00
parent 448c549698
commit 75dc720198
10312 changed files with 2711566 additions and 671451 deletions
@@ -0,0 +1,534 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <EditorPythonBindings/CustomTypeBindingBus.h>
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonProxyObject.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Math/MathUtils.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AZ
{
template <typename T>
struct CustomType
{
CustomType() = default;
CustomType(T value)
{
m_value = value;
}
T m_value = {};
};
struct MyCustomData
{
AZ::s32 s32Field = -32;
AZ::u32 u32Field = 32;
AZ::s16 s16Field = -16;
AZ::u16 u16Field = 16;
bool Compare(const MyCustomData& other) const
{
return other.s32Field == s32Field
&& other.u32Field == u32Field
&& other.s16Field == s16Field
&& other.u16Field == u16Field;
}
};
AZ_TYPE_INFO_SPECIALIZE(CustomType<int>, "{78BFA28F-7FF3-4DC6-B9E9-2DF158E6496B}");
AZ_TYPE_INFO_SPECIALIZE(CustomType<float>, "{4B71C5C7-6947-4510-88A6-87F9F975F9CB}");
AZ_TYPE_INFO_SPECIALIZE(CustomType<AZStd::string>, "{61ED57E0-50B2-4AD7-997A-FD343A964C49}");
AZ_TYPE_INFO_SPECIALIZE(CustomType<MyCustomData>, "{839E35B3-14EF-4776-A5A1-C7B914374A66}");
}
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// test class/struts
struct CustomTypeHandlerTester final
{
AZ_TYPE_INFO(CustomTypeHandlerTester, "{C59220A9-1479-434C-BBBD-4262090507FA}");
AZ::CustomType<int> CreateCustomTypeInt(int value)
{
return AZ::CustomType<int>(value);
}
int ReturnCustomTypeInt(const AZ::CustomType<int>& value) const
{
return value.m_value;
}
AZ::CustomType<float> CreateCustomTypeFloat(float value)
{
return AZ::CustomType<float>(value);
}
float ReturnCustomTypeFloat(const AZ::CustomType<float>& value) const
{
return value.m_value;
}
bool CompareCustomTypeFloatValues(const AZ::CustomType<float>& lhs, const AZ::CustomType<float>& rhs) const
{
return AZ::IsClose(lhs.m_value, rhs.m_value, std::numeric_limits<float>::epsilon());
}
AZ::CustomType<AZStd::string> CreateCustomTypeString(const AZStd::string& value)
{
return AZ::CustomType<AZStd::string>(value);
}
AZ::CustomType<AZStd::string> CombineCustomTypeString(const AZ::CustomType<AZStd::string>& lhs, const AZ::CustomType<AZStd::string>& rhs)
{
return AZ::CustomType<AZStd::string>(lhs.m_value + rhs.m_value);
}
AZStd::string ReturnCustomTypeString(const AZ::CustomType<AZStd::string>& value)
{
return value.m_value;
}
AZ::CustomType<AZ::MyCustomData> CreateCustomData(
AZ::s32 s32Value,
AZ::u32 u32Value,
AZ::s16 s16Value,
AZ::u16 u16Value)
{
auto value = AZ::CustomType<AZ::MyCustomData>();
value.m_value.s32Field = s32Value;
value.m_value.u32Field = u32Value;
value.m_value.s16Field = s16Value;
value.m_value.u16Field = u16Value;
return value;
}
AZ::CustomType<AZ::MyCustomData> CombineCustomData(const AZ::CustomType<AZ::MyCustomData>& lhs, const AZ::CustomType<AZ::MyCustomData>& rhs)
{
AZ::CustomType<AZ::MyCustomData> combined;
combined.m_value.s32Field = lhs.m_value.s32Field + rhs.m_value.s32Field;
combined.m_value.u32Field = lhs.m_value.u32Field + rhs.m_value.u32Field;
combined.m_value.s16Field = lhs.m_value.s16Field + rhs.m_value.s16Field;
combined.m_value.u16Field = lhs.m_value.u16Field + rhs.m_value.u16Field;
return combined;
}
void Reflect(AZ::ReflectContext* context)
{
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<CustomTypeHandlerTester>("CustomTypeHandlerTester")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Method("CreateCustomTypeInt", &CustomTypeHandlerTester::CreateCustomTypeInt)
->Method("ReturnCustomTypeInt", &CustomTypeHandlerTester::ReturnCustomTypeInt)
->Method("CreateCustomTypeFloat", &CustomTypeHandlerTester::CreateCustomTypeFloat)
->Method("ReturnCustomTypeFloat", &CustomTypeHandlerTester::ReturnCustomTypeFloat)
->Method("CompareCustomTypeFloatValues", &CustomTypeHandlerTester::CompareCustomTypeFloatValues)
->Method("CreateCustomTypeString", &CustomTypeHandlerTester::CreateCustomTypeString)
->Method("CombineCustomTypeString", &CustomTypeHandlerTester::CombineCustomTypeString)
->Method("ReturnCustomTypeString", &CustomTypeHandlerTester::ReturnCustomTypeString)
->Method("CreateCustomData", &CustomTypeHandlerTester::CreateCustomData)
->Method("CombineCustomData", &CustomTypeHandlerTester::CombineCustomData)
;
}
}
};
struct CustomTypeBindingNotificationBusHandler final
: public EditorPythonBindings::CustomTypeBindingNotificationBus::MultiHandler
{
using Handle = EditorPythonBindings::CustomTypeBindingNotifications::ValueHandle;
constexpr static Handle NoAllocation { ~0LL };
AZStd::unordered_map<void*, AZ::TypeId> m_allocationMap;
CustomTypeBindingNotificationBusHandler()
{
BusConnect(azrtti_typeid<AZ::CustomType<int>>());
BusConnect(azrtti_typeid<AZ::CustomType<float>>());
BusConnect(azrtti_typeid<AZ::CustomType<AZStd::string>>());
BusConnect(azrtti_typeid<AZ::CustomType<AZ::MyCustomData>>());
}
~CustomTypeBindingNotificationBusHandler()
{
BusDisconnect();
}
using AllocationHandle = EditorPythonBindings::CustomTypeBindingNotifications::AllocationHandle;
AllocationHandle AllocateDefault() override
{
AZ::BehaviorObject behaviorObject;
const AZ::TypeId& typeId = *EditorPythonBindings::CustomTypeBindingNotificationBus::GetCurrentBusId();
if (typeId == azrtti_typeid<AZ::CustomType<int>>())
{
behaviorObject.m_address = azmalloc(sizeof(AZ::CustomType<int>));
behaviorObject.m_typeId = typeId;
m_allocationMap[behaviorObject.m_address] = behaviorObject.m_typeId;
return { {reinterpret_cast<Handle>(behaviorObject.m_address), AZStd::move(behaviorObject)} };
}
else if (typeId == azrtti_typeid<AZ::CustomType<float>>())
{
behaviorObject.m_address = azmalloc(sizeof(AZ::CustomType<float>));
behaviorObject.m_typeId = typeId;
m_allocationMap[behaviorObject.m_address] = behaviorObject.m_typeId;
return { {reinterpret_cast<Handle>(behaviorObject.m_address), AZStd::move(behaviorObject)} };
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZStd::string>>())
{
behaviorObject.m_address = new AZ::CustomType<AZStd::string>();
behaviorObject.m_typeId = typeId;
m_allocationMap[behaviorObject.m_address] = behaviorObject.m_typeId;
return { {reinterpret_cast<Handle>(behaviorObject.m_address), AZStd::move(behaviorObject)} };
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZ::MyCustomData>>())
{
behaviorObject.m_address = azmalloc(sizeof(AZ::CustomType<AZ::MyCustomData>));
new (behaviorObject.m_address) AZ::CustomType<AZ::MyCustomData>();
behaviorObject.m_typeId = typeId;
m_allocationMap[behaviorObject.m_address] = behaviorObject.m_typeId;
return { {reinterpret_cast<Handle>(behaviorObject.m_address), AZStd::move(behaviorObject)} };
}
return AZStd::nullopt;
}
AZStd::optional<ValueHandle> PythonToBehavior(
PyObject* pyObj,
[[maybe_unused]] AZ::BehaviorParameter::Traits traits,
AZ::BehaviorValueParameter& outValue) override
{
const AZ::TypeId& typeId = *EditorPythonBindings::CustomTypeBindingNotificationBus::GetCurrentBusId();
if (typeId == azrtti_typeid<AZ::CustomType<int>>())
{
outValue.ConvertTo<AZ::CustomType<int>>();
outValue.StoreInTempData<AZ::CustomType<int>>({ aznumeric_cast<int>(PyLong_AsLong(pyObj)) });
return { NoAllocation };
}
else if (typeId == azrtti_typeid<AZ::CustomType<float>>())
{
float floatValue = aznumeric_cast<float>(PyFloat_AsDouble(pyObj));
outValue.ConvertTo<AZ::CustomType<float>>();
outValue.StoreInTempData<AZ::CustomType<float>>({ floatValue });
return { NoAllocation };
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZStd::string>>())
{
if (PyUnicode_Check(pyObj))
{
Py_ssize_t pySize = 0;
const char* pyData = PyUnicode_AsUTF8AndSize(pyObj, &pySize);
if (pyData)
{
auto data = new AZ::CustomType<AZStd::string>();
data->m_value.assign(pyData, pyData + pySize);
outValue.ConvertTo<AZ::CustomType<AZStd::string>>();
outValue.m_value = data;
m_allocationMap[outValue.m_value] = typeId;
return { reinterpret_cast<Handle>(outValue.m_value) };
}
return { NoAllocation };
}
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZ::MyCustomData>>())
{
if (PyTuple_Check(pyObj) && PyTuple_Size(pyObj) == 4)
{
void* data = azmalloc(sizeof(AZ::CustomType<AZ::MyCustomData>));
new (data) AZ::CustomType<AZ::MyCustomData>();
m_allocationMap[data] = typeId;
AZ::CustomType<AZ::MyCustomData>* myData = reinterpret_cast<AZ::CustomType<AZ::MyCustomData>*>(data);
myData->m_value.s32Field = aznumeric_cast<AZ::s32>(PyLong_AsLong(PyTuple_GetItem(pyObj, 0)));
myData->m_value.u32Field = aznumeric_cast<AZ::u32>(PyLong_AsLong(PyTuple_GetItem(pyObj, 1)));
myData->m_value.s16Field = aznumeric_cast<AZ::s16>(PyLong_AsLong(PyTuple_GetItem(pyObj, 2)));
myData->m_value.u16Field = aznumeric_cast<AZ::u16>(PyLong_AsLong(PyTuple_GetItem(pyObj, 3)));
outValue.ConvertTo<AZ::CustomType<AZ::MyCustomData>>();
outValue.m_value = data;
return { reinterpret_cast<Handle>(data) };
}
}
return AZStd::nullopt;
}
AZStd::optional<ValueHandle> BehaviorToPython(
const AZ::BehaviorValueParameter& behaviorValue,
PyObject*& outPyObj) override
{
const AZ::TypeId& typeId = *EditorPythonBindings::CustomTypeBindingNotificationBus::GetCurrentBusId();
if (typeId == azrtti_typeid<AZ::CustomType<int>>())
{
AZ::CustomType<int>* value = behaviorValue.GetAsUnsafe<AZ::CustomType<int>>();
outPyObj = PyLong_FromLong(value->m_value);
return { NoAllocation };
}
else if (typeId == azrtti_typeid<AZ::CustomType<float>>())
{
AZ::CustomType<float>* value = behaviorValue.GetAsUnsafe<AZ::CustomType<float>>();
outPyObj = PyFloat_FromDouble(value->m_value);
return { NoAllocation };
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZStd::string>>())
{
AZ::CustomType<AZStd::string>* value = behaviorValue.GetAsUnsafe<AZ::CustomType<AZStd::string>>();
outPyObj = PyUnicode_FromString(value->m_value.c_str());
return { NoAllocation };
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZ::MyCustomData>>())
{
AZ::CustomType<AZ::MyCustomData>* value = behaviorValue.GetAsUnsafe<AZ::CustomType<AZ::MyCustomData>>();
outPyObj = PyTuple_New(4);
PyTuple_SetItem(outPyObj, 0, PyLong_FromLong(value->m_value.s32Field));
PyTuple_SetItem(outPyObj, 1, PyLong_FromLong(value->m_value.u32Field));
PyTuple_SetItem(outPyObj, 2, PyLong_FromLong(value->m_value.s16Field));
PyTuple_SetItem(outPyObj, 3, PyLong_FromLong(value->m_value.u16Field));
return { NoAllocation };
}
return AZStd::nullopt;
}
bool CanConvertPythonToBehavior(
[[maybe_unused]] AZ::BehaviorParameter::Traits traits,
PyObject* pyObj) const override
{
const AZ::TypeId& typeId = *EditorPythonBindings::CustomTypeBindingNotificationBus::GetCurrentBusId();
if (typeId == azrtti_typeid<AZ::CustomType<int>>())
{
return PyLong_Check(pyObj);
}
else if (typeId == azrtti_typeid<AZ::CustomType<float>>())
{
return PyFloat_Check(pyObj);
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZStd::string>>())
{
return PyUnicode_Check(pyObj);
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZ::MyCustomData>>())
{
return PyTuple_Check(pyObj);
}
return false;
}
void CleanUpValue(ValueHandle handle) override
{
auto handleEntry = m_allocationMap.find(reinterpret_cast<void*>(handle));
if (handleEntry != m_allocationMap.end())
{
m_allocationMap.erase(handleEntry);
const AZ::TypeId& typeId = handleEntry->second;
if (typeId == azrtti_typeid<AZ::CustomType<int>>())
{
azfree(reinterpret_cast<void*>(handle));
}
else if (typeId == azrtti_typeid<AZ::CustomType<float>>())
{
azfree(reinterpret_cast<void*>(handle));
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZStd::string>>())
{
delete reinterpret_cast<AZ::CustomType<AZStd::string>*>(handle);
}
else if (typeId == azrtti_typeid<AZ::CustomType<AZ::MyCustomData>>())
{
azfree(reinterpret_cast<void*>(handle));
}
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct CustomTypeHandlerTests
: public PythonTestingFixture
{
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
PythonTestingFixture::TearDown();
}
};
//////////////////////////////////////////////////////////////////////////
// tests
TEST_F(CustomTypeHandlerTests, CustomTypeHandler_ReturnsCustom_Works)
{
CustomTypeBindingNotificationBusHandler customTypeBindingNotificationBusHandler;
CustomTypeHandlerTester customTypeHandlerTester;
customTypeHandlerTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test
tester = azlmbr.test.CustomTypeHandlerTester()
customValue = tester.CreateCustomTypeInt(42)
if (None == customValue):
raise RuntimeError('None == customValue')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
e.Deactivate();
}
TEST_F(CustomTypeHandlerTests, CustomTypeHandler_AcceptsCustom_Works)
{
CustomTypeBindingNotificationBusHandler customTypeBindingNotificationBusHandler;
CustomTypeHandlerTester customTypeHandlerTester;
customTypeHandlerTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test
tester = azlmbr.test.CustomTypeHandlerTester()
customValue = tester.CreateCustomTypeInt(42)
value = tester.ReturnCustomTypeInt(customValue)
if (value != 42):
raise RuntimeError('value != 42')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
e.Deactivate();
}
TEST_F(CustomTypeHandlerTests, CustomTypeHandler_CustomFloatValues_Works)
{
CustomTypeBindingNotificationBusHandler customTypeBindingNotificationBusHandler;
CustomTypeHandlerTester customTypeHandlerTester;
customTypeHandlerTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test
tester = azlmbr.test.CustomTypeHandlerTester()
lhsValue = tester.CreateCustomTypeFloat(42.0)
rhsValue = tester.CreateCustomTypeFloat(tester.ReturnCustomTypeFloat(lhsValue))
if (tester.CompareCustomTypeFloatValues(lhsValue,rhsValue) is False):
raise RuntimeError('tester.CompareCustomTypeFloatValues(lhsValue,rhsValue) is False')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
e.Deactivate();
}
TEST_F(CustomTypeHandlerTests, CustomTypeHandler_CustomStringValues_Works)
{
CustomTypeBindingNotificationBusHandler customTypeBindingNotificationBusHandler;
CustomTypeHandlerTester customTypeHandlerTester;
customTypeHandlerTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test
tester = azlmbr.test.CustomTypeHandlerTester()
babble = tester.CreateCustomTypeString('babble')
fish = tester.CreateCustomTypeString('fish')
babbleFish = tester.CombineCustomTypeString(babble, fish)
if (tester.ReturnCustomTypeString(babbleFish) != 'babblefish'):
raise RuntimeError("tester.ReturnCustomTypeString(babbleFish) != 'babblefish'")
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
e.Deactivate();
}
TEST_F(CustomTypeHandlerTests, CustomTypeHandler_CustomDataValue_Works)
{
CustomTypeBindingNotificationBusHandler customTypeBindingNotificationBusHandler;
CustomTypeHandlerTester customTypeHandlerTester;
customTypeHandlerTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test
tester = azlmbr.test.CustomTypeHandlerTester()
lhs = tester.CreateCustomData(-1, 1, -2, 2)
rhs = tester.CreateCustomData(0, 0, 1, 1)
outTuple = tester.CombineCustomData(lhs, rhs)
if (outTuple[0] != -1 or outTuple[1] != 1 or outTuple[2] != -1 or outTuple[3] != 3):
raise RuntimeError("outTuple[0] != -1 or outTuple[1] != 1 or outTuple[2] != -2 or outTuple[3] != 2")
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
e.Deactivate();
}
}
@@ -0,0 +1,469 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <EditorPythonBindings/EditorPythonBindingsBus.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
namespace UnitTest
{
struct EditorPythonBindingsNotificationBusSink final
: public EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler
{
EditorPythonBindingsNotificationBusSink()
{
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusConnect();
}
~EditorPythonBindingsNotificationBusSink()
{
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
}
//////////////////////////////////////////////////////////////////////////
// handles EditorPythonBindingsNotificationBus
int m_OnPreInitializeCount = 0;
int m_OnPostInitializeCount = 0;
int m_OnPreFinalizeCount = 0;
int m_OnPostFinalizeCount = 0;
void OnPreInitialize() override { m_OnPreInitializeCount++; }
void OnPostInitialize() override { m_OnPostInitializeCount++; }
void OnPreFinalize() override { m_OnPreFinalizeCount++; }
void OnPostFinalize() override { m_OnPostFinalizeCount++; }
};
class EditorPythonBindingsTest
: public PythonTestingFixture
{
public:
PythonTraceMessageSink m_testSink;
EditorPythonBindingsNotificationBusSink m_notificationSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonSystemComponent::CreateDescriptor());
}
void TearDown() override
{
// clearing up memory
m_notificationSink = EditorPythonBindingsNotificationBusSink();
m_testSink = PythonTraceMessageSink();
// shutdown time!
PythonTestingFixture::TearDown();
}
};
TEST_F(EditorPythonBindingsTest, FireUpPythonVM)
{
enum class LogTypes
{
Skip = 0,
General,
RedirectOutputInstalled
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "RedirectOutput installed"))
{
return static_cast<int>(LogTypes::RedirectOutputInstalled);
}
return static_cast<int>(LogTypes::General);
}
return static_cast<int>(LogTypes::Skip);
};
AZ::Entity e;
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.Init();
e.Activate();
SimulateEditorBecomingInitialized();
e.Deactivate();
EXPECT_GT(m_testSink.m_evaluationMap[(int)LogTypes::General], 0);
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::RedirectOutputInstalled], 1);
EXPECT_EQ(m_notificationSink.m_OnPreInitializeCount, 1);
EXPECT_EQ(m_notificationSink.m_OnPostFinalizeCount, 1);
EXPECT_EQ(m_notificationSink.m_OnPreFinalizeCount, 1);
EXPECT_EQ(m_notificationSink.m_OnPostFinalizeCount, 1);
}
TEST_F(EditorPythonBindingsTest, RunScriptTextBuffer)
{
enum class LogTypes
{
Skip = 0,
ScriptWorked
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "EditorPythonBindingsTest_RunScriptTextBuffer"))
{
return static_cast<int>(LogTypes::ScriptWorked);
}
}
return static_cast<int>(LogTypes::Skip);
};
AZ::Entity e;
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.Init();
e.Activate();
SimulateEditorBecomingInitialized();
const char* script =
R"(
import sys
print ('EditorPythonBindingsTest_RunScriptTextBuffer')
)";
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, false);
e.Deactivate();
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::ScriptWorked], 1);
}
TEST_F(EditorPythonBindingsTest, RunScriptTextBufferAndPrint)
{
AZ::Entity e;
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.Init();
e.Activate();
SimulateEditorBecomingInitialized();
AZStd::string capturedOutput;
m_testSink.m_evaluateMessage = [&capturedOutput](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
capturedOutput.append(message);
}
return 0;
};
// Expressions should log their result
// Any other statement shouldn't log anything
capturedOutput.clear();
const char* script = "5+5";
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true);
EXPECT_EQ(capturedOutput, "10\n");
capturedOutput.clear();
script =
R"(
import sys
sys.version
)";
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true);
EXPECT_EQ(capturedOutput, "");
capturedOutput.clear();
script = "variable = 'test'";
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true);
EXPECT_EQ(capturedOutput, "");
capturedOutput.clear();
script = "variable";
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true);
EXPECT_EQ(capturedOutput, "test\n");
}
TEST_F(EditorPythonBindingsTest, RunScriptFile)
{
enum class LogTypes
{
Skip = 0,
RanFromFile
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
AZStd::string_view m(message);
if (AzFramework::StringFunc::Equal(message, "EditorPythonBindingsTest_RunScriptFile"))
{
return static_cast<int>(LogTypes::RanFromFile);
}
}
return static_cast<int>(LogTypes::Skip);
};
AZStd::string filename;
AzFramework::StringFunc::Path::ConstructFull(m_engineRoot, "Gems/EditorPythonBindings/Code/Tests", "EditorPythonBindingsTest", "py", filename);
AZ::Entity e;
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.Init();
e.Activate();
SimulateEditorBecomingInitialized();
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilename, filename.c_str());
e.Deactivate();
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::RanFromFile], 1);
}
TEST_F(EditorPythonBindingsTest, RunScriptFileWithArgs)
{
enum class LogTypes
{
Skip = 0,
RanFromFile,
NumArgsCorrect,
ScriptNameCorrect,
Arg1Correct,
Arg2Correct,
Arg3Correct
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
AZStd::string_view m(message);
if (AzFramework::StringFunc::Equal(message, "EditorPythonBindingsTestWithArgs_RunScriptFile"))
{
return static_cast<int>(LogTypes::RanFromFile);
}
else if (AzFramework::StringFunc::Equal(message, "num args: 4"))
{
return static_cast<int>(LogTypes::NumArgsCorrect);
}
else if (AzFramework::StringFunc::Equal(message, "script name: EditorPythonBindingsTestWithArgs.py"))
{
return static_cast<int>(LogTypes::ScriptNameCorrect);
}
else if (AzFramework::StringFunc::Equal(message, "arg 1: arg1"))
{
return static_cast<int>(LogTypes::Arg1Correct);
}
else if (AzFramework::StringFunc::Equal(message, "arg 2: 2"))
{
return static_cast<int>(LogTypes::Arg2Correct);
}
else if (AzFramework::StringFunc::Equal(message, "arg 3: arg3"))
{
return static_cast<int>(LogTypes::Arg3Correct);
}
}
return static_cast<int>(LogTypes::Skip);
};
AZStd::string filename;
AzFramework::StringFunc::Path::ConstructFull(m_engineRoot, "Gems/EditorPythonBindings/Code/Tests", "EditorPythonBindingsTestWithArgs", "py", filename);
AZ::Entity e;
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.Init();
e.Activate();
SimulateEditorBecomingInitialized();
AZStd::vector<AZStd::string_view> args;
args.push_back("arg1");
args.push_back("2");
args.push_back("arg3");
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, filename.c_str(), args);
e.Deactivate();
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::RanFromFile], 1);
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::NumArgsCorrect], 1);
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::ScriptNameCorrect], 1);
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::Arg1Correct], 1);
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::Arg2Correct], 1);
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::Arg3Correct], 1);
}
//
// Tests that makes sure that basic Python libraries can be loaded
//
class EditorPythonBindingsLibraryTest
: public PythonTestingFixture
{
public:
PythonTraceMessageSink m_testSink;
EditorPythonBindingsNotificationBusSink m_notificationSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonSystemComponent::CreateDescriptor());
}
void TearDown() override
{
// clearing up memory
m_notificationSink = EditorPythonBindingsNotificationBusSink();
m_testSink = PythonTraceMessageSink();
// shutdown time!
PythonTestingFixture::TearDown();
}
void DoLibraryTest(const char* libName)
{
bool executedLine = false;
m_testSink.m_evaluateMessage = [&executedLine](const char* window, const char* message)
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "python_vm_loaded_lib"))
{
executedLine = true;
}
}
return false;
};
AZ::Entity e;
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.Init();
e.Activate();
try
{
SimulateEditorBecomingInitialized();
const bool printResult = false;
AZStd::string script(AZStd::string::format("import %s\nprint ('python_vm_loaded_lib')", libName));
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString,
script.c_str(),
printResult);
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
e.Deactivate();
EXPECT_TRUE(executedLine);
}
};
// This test makes sure that some of the expected built-in libraries
// Are present in the version of python we are using (the ones most problematic for building)
TEST_F(EditorPythonBindingsTest, VerifyExpectedLibrariesPresent)
{
enum class LogTypes
{
Skip = 0,
ScriptWorked
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "EditorPythonBindingsTest_VerifyExpectedLibrariesPresent"))
{
return static_cast<int>(LogTypes::ScriptWorked);
}
}
return static_cast<int>(LogTypes::Skip);
};
AZ::Entity e;
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.Init();
e.Activate();
SimulateEditorBecomingInitialized();
const char* script =
R"(
import sys
import sqlite3
import ssl
print ('EditorPythonBindingsTest_VerifyExpectedLibrariesPresent')
)";
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true);
e.Deactivate();
EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::ScriptWorked], 1);
}
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_sys_Works)
{
DoLibraryTest("sys");
}
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_ctypes_Works)
{
DoLibraryTest("ctypes");
}
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_bz2_Works)
{
DoLibraryTest("bz2");
}
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_lzma_Works)
{
DoLibraryTest("lzma");
}
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_socket_Works)
{
DoLibraryTest("socket");
}
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_sqlite3_Works)
{
DoLibraryTest("sqlite3");
}
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_ssl_Works)
{
DoLibraryTest("ssl");
}
// This library lives in Editor/Scripts. We're testing that our sys.path extension code in ExtendSysPath works as expected
TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_SysPathExtendedToGemScripts_EditorPythonBindingsValidaitonFound)
{
DoLibraryTest("editor_script_validation");
}
}
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -0,0 +1,17 @@
"""
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.
"""
#
# testing Python code
#
import sys
print ('EditorPythonBindingsTest_RunScriptFile')
@@ -0,0 +1,29 @@
"""
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.
"""
#
# testing Python code
#
import sys
import os
import os.path
print('EditorPythonBindingsTestWithArgs_RunScriptFile')
print('num args: {}'.format(len(sys.argv)))
# Intentionally print script name separately from the other args.
# The path that it prints will be non-deterministic based on where the code
# has been synced to, so we strip it off, enabling us to just validate the script name
# and the other args made it through successfully.
print('script name: {}'.format(os.path.basename(sys.argv[0])))
for arg in range(1, len(sys.argv)):
print('arg {}: {}'.format(arg, sys.argv[arg]))
@@ -0,0 +1,792 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Asset/SimpleAsset.h>
namespace UnitTest
{
class FooMockSimpleAsset
{
public:
AZ_TYPE_INFO(FooMockSimpleAsset, "{0298F78A-77EF-47CE-9912-B0BC80060016}");
static const char* GetFileFilter()
{
return "foo";
}
};
}
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// test class/struts
struct MockBinding final
{
AZ_TYPE_INFO(MockBinding, "{0B22887C-6377-4573-8FE5-418947640D3F}");
AZ::Data::AssetId m_mockAssetId;
MockBinding() = default;
MockBinding(const AZ::Data::AssetId& value)
{
m_mockAssetId = value;
}
const AZ::Data::AssetId& GetAssetId() const
{
return m_mockAssetId;
}
static void Reflect(AZ::ReflectContext* reflection)
{
auto&& behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection);
if (behaviorContext)
{
behaviorContext->Class<MockBinding>("MockBinding")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "mock")
->Constructor()
->Constructor<const AZ::Data::AssetId&>()
->Method("GetAssetId", &MockBinding::GetAssetId)
;
}
}
};
class MockAsset
: public AzFramework::SimpleAssetReferenceBase
{
public:
AZ_RTTI(MockAsset, "{C783597C-568F-4B94-911C-506CBD161E10}", AzFramework::SimpleAssetReferenceBase);
MockAsset()
{
SetAssetPath("a/fake/path.foo");
}
static void Reflect(AZ::ReflectContext* reflection)
{
auto&& serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<MockAsset, AzFramework::SimpleAssetReferenceBase>();
AzFramework::SimpleAssetReference<FooMockSimpleAsset>::Register(*serializeContext);
}
auto&& behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection);
if (behaviorContext)
{
behaviorContext->Class<MockAsset>("MockAsset")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
;
}
}
AZ::Data::AssetType GetAssetType() const override
{
// Use an arbitrary ID for the asset type.
return AZ::Data::AssetType("{7FD86523-3903-4037-BCD1-542027BFC553}");
}
virtual const char* GetFileFilter() const
{
return nullptr;
}
};
struct MockAssetData
: public AZ::Data::AssetData
{
void SetUseCount(AZ::s32 value)
{
m_useCount = value;
}
void SetAssetId(AZ::Data::AssetId value)
{
m_assetId = value;
}
};
struct MyTestAssetData
: public AZ::Data::AssetData
{
AZ_RTTI(MyTestAssetData, "{B78C6629-95F4-4211-AE7F-4DE58C0D3C33}", AZ::Data::AssetData);
AZ::u64 m_number = 0;
void SetUseCount(AZ::s32 value)
{
m_useCount = value;
}
};
class ClassWithAssets
{
public:
AZ_RTTI(ClassWithAssets, "{06E4DC78-DD42-44A8-83A1-5B333B557DE9}");
virtual ~ClassWithAssets() = default;
static void Reflect(AZ::ReflectContext* reflection)
{
auto&& serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<ClassWithAssets>()
->Field("assetId", &ClassWithAssets::m_assetId)
->Field("assetData", &ClassWithAssets::m_assetData)
->Field("mockAsset", &ClassWithAssets::m_mockAsset)
->Field("simpleAssetReference", &ClassWithAssets::m_simpleAssetReference)
;
}
auto&& behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection);
if (behaviorContext)
{
behaviorContext->Class<ClassWithAssets>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Property("assetId", BehaviorValueProperty(&ClassWithAssets::m_assetId))
->Property("assetData", BehaviorValueProperty(&ClassWithAssets::m_assetData))
->Property("mockAsset", BehaviorValueProperty(&ClassWithAssets::m_mockAsset))
->Property("simpleAssetReference", BehaviorValueProperty(&ClassWithAssets::m_simpleAssetReference))
->Method("createFooMockSimpleAsset",&ClassWithAssets::CreateFooMockSimpleAsset)
->Method("printFooMockSimpleAsset", &ClassWithAssets::PrintFooMockSimpleAsset)
;
}
}
AzFramework::SimpleAssetReference<FooMockSimpleAsset> CreateFooMockSimpleAsset(AZStd::string_view assetPath)
{
AZ_TracePrintf("python", "SimpleAssetReference creating asset for path %.*s", static_cast<int>(assetPath.size()), assetPath.data());
AzFramework::SimpleAssetReference<FooMockSimpleAsset> fooMockSimpleAsset;
fooMockSimpleAsset.SetAssetPath(assetPath.data());
return fooMockSimpleAsset;
}
void PrintFooMockSimpleAsset([[maybe_unused]] AzFramework::SimpleAssetReference<FooMockSimpleAsset>& fooMockSimpleAsset)
{
AZ_TracePrintf("python", "SimpleAssetReference asset path is (%s) \n", fooMockSimpleAsset.GetAssetPath().c_str());
}
AZ::Data::AssetId m_assetId = AZ::Data::AssetId(AZ::Uuid::Create(), 512);
MockAsset m_mockAsset;
AZ::Data::Asset<AZ::Data::AssetData> m_assetData;
AzFramework::SimpleAssetReference<FooMockSimpleAsset> m_simpleAssetReference;
};
namespace Internal
{
MockAsset s_mockAsset;
MockAssetData s_mockAssetData;
AZ::Data::Asset<AZ::Data::AssetData> s_asset;
AZ::Data::AssetId s_assetId;
}
struct PythonReflectionAssetTypes
{
AZ_TYPE_INFO(PythonReflectionAssetTypes, "{04C929EE-67FA-4BDB-BC56-3680D61C9DEC}");
AZ::Data::AssetId m_assetId;
AZ::Data::Asset<AZ::Data::AssetData> m_assetData;
AZ::Data::Asset<MyTestAssetData> m_myTestAssetDataAsset;
MyTestAssetData m_testAssetData;
ClassWithAssets m_mockDescriptor;
AZStd::unique_ptr<MyTestAssetData> m_myTestAssetData;
PythonReflectionAssetTypes()
{
m_testAssetData.m_number = 2;
m_myTestAssetDataAsset = AZ::Data::Asset<MyTestAssetData>(
static_cast<AZ::Data::AssetData*>(&m_testAssetData),
AZ::Data::AssetLoadBehavior::NoLoad);
m_assetId.m_guid = AZ::Uuid::CreateRandom();
m_assetId.m_subId = 1234;
}
~PythonReflectionAssetTypes()
{
// manually releasing the m_testAssetData
m_testAssetData.SetUseCount(2);
m_testAssetData.AcquireWeak();
m_myTestAssetDataAsset = {};
}
static void PrintAssetData([[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& assetData)
{
AZ_TracePrintf("python", "Asset Data ID = %s\n",
assetData.GetId().ToString<AZStd::string>().c_str());
}
static void PrintSimpleAssetReference([[maybe_unused]] const AzFramework::SimpleAssetReferenceBase& simpleAssetRef)
{
AZ_TracePrintf("python", "SimpleAssetReference of asset type = %s\n",
simpleAssetRef.GetAssetType().ToString<AZStd::string>().c_str());
}
static AZ::Data::Asset<AZ::Data::AssetData> GenerateAsset()
{
Internal::s_assetId = AZ::Data::AssetId(AZ::Uuid::Create(), 42);
Internal::s_mockAssetData.SetAssetId(Internal::s_assetId);
Internal::s_asset = AZ::Data::Asset<AZ::Data::AssetData>(
static_cast<AZ::Data::AssetData*>(&Internal::s_mockAssetData),
AZ::Data::AssetLoadBehavior::NoLoad);
return Internal::s_asset;
}
static AZ::Data::AssetId CreateAssetId(AZStd::string_view assetUuid)
{
return AZ::Data::AssetId::CreateString(assetUuid);
}
static bool CompareAssetIds(const AZ::Data::AssetId& lhs, const AZ::Data::AssetId& rhs)
{
return lhs == rhs;
}
static bool CompareAssetData(const AZ::Data::Asset<AZ::Data::AssetData>& lhs, const AZ::Data::Asset<AZ::Data::AssetData>& rhs)
{
const bool sameId = lhs.GetId() == rhs.GetId();
const bool sameType = lhs.GetType() == rhs.GetType();
const bool sameHint = lhs.GetHint() == rhs.GetHint();
return sameId && sameType && sameHint;
}
static bool CompareMockAssets(const MockAsset& lhs, const MockAsset& rhs)
{
return lhs.GetAssetPath() == rhs.GetAssetPath();
}
AZ::Data::Asset<MyTestAssetData> CreateMyTestAssetData()
{
m_myTestAssetData = AZStd::make_unique<MyTestAssetData>();
m_myTestAssetData->m_number = 42;
return AZ::Data::Asset<MyTestAssetData>(
static_cast<AZ::Data::AssetData*>(m_myTestAssetData.get()),
AZ::Data::AssetLoadBehavior::NoLoad);
}
void ReadMyTestAssetData(const AZ::Data::Asset<MyTestAssetData>& data)
{
if (data.Get())
{
AZ_TracePrintf("python", "AssetData: MyTestAssetData read in data \n");
}
}
AZ::Data::Asset<AZ::Data::AssetData> CreateAssetHandle(const AZ::Data::AssetId& assetId)
{
return AZ::Data::Asset<AZ::Data::AssetData>(assetId, m_mockDescriptor.m_mockAsset.GetAssetType(), "test");
}
void Reflect(AZ::ReflectContext* context)
{
ClassWithAssets::Reflect(context);
MockAsset::Reflect(context);
auto&& serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<PythonReflectionAssetTypes>()
->Field("assetId", &PythonReflectionAssetTypes::m_assetId)
->Field("assetData", &PythonReflectionAssetTypes::m_assetData)
->Field("myTestAssetData", &PythonReflectionAssetTypes::m_myTestAssetData)
->Field("mockDescriptor", &PythonReflectionAssetTypes::m_mockDescriptor)
;
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<PythonReflectionAssetTypes>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
// class methods
->Method("compare_asset_ids", &PythonReflectionAssetTypes::CompareAssetIds)
->Method("compare_asset_data", &PythonReflectionAssetTypes::CompareAssetData)
->Method("compare_mock_assets", &PythonReflectionAssetTypes::CompareMockAssets)
->Method("create_asset_id", &PythonReflectionAssetTypes::CreateAssetId)
->Method("print_asset_data", &PythonReflectionAssetTypes::PrintAssetData)
->Method("print_simple_asset_reference", &PythonReflectionAssetTypes::PrintSimpleAssetReference)
->Method("generate_asset", &PythonReflectionAssetTypes::GenerateAsset)
// instance methods
->Method("create_asset_handle", &PythonReflectionAssetTypes::CreateAssetHandle)
->Method("create_my_test_asset_data", &PythonReflectionAssetTypes::CreateMyTestAssetData)
->Method("read_my_test_asset_data", &PythonReflectionAssetTypes::ReadMyTestAssetData)
// instance properties
->Property("assetId", BehaviorValueProperty(&PythonReflectionAssetTypes::m_assetId))
->Property("assetData", BehaviorValueProperty(&PythonReflectionAssetTypes::m_assetData))
->Property("mockDescriptor", BehaviorValueProperty(&PythonReflectionAssetTypes::m_mockDescriptor))
->Property("myTestAssetDataAsset", BehaviorValueProperty(&PythonReflectionAssetTypes::m_myTestAssetDataAsset))
;
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonAssetTypesTests
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
};
TEST_F(PythonAssetTypesTests, AssetOnDemand)
{
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetSerializeContext());
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetBehaviorContext());
PythonReflectionAssetTypes pythonReflectionAssetTypes;
pythonReflectionAssetTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionAssetTypes.Reflect(m_app.GetBehaviorContext());
// make sure expected class names exist in the Behavior Context
auto&& behaviorClasses = m_app.GetBehaviorContext()->m_classes;
EXPECT_TRUE(behaviorClasses.find("Asset<AssetData>") != behaviorClasses.end());
EXPECT_TRUE(behaviorClasses.find("Asset<MyTestAssetData>") != behaviorClasses.end());
EXPECT_TRUE(behaviorClasses.find("SimpleAssetReferenceBase") != behaviorClasses.end());
EXPECT_TRUE(behaviorClasses.find("SimpleAssetReference<AssetType><FooMockSimpleAsset >") != behaviorClasses.end());
}
TEST_F(PythonAssetTypesTests, AssetIdValues)
{
enum class LogTypes
{
Skip = 0,
AssetId,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "AssetId"))
{
return static_cast<int>(LogTypes::AssetId);
}
}
return static_cast<int>(LogTypes::Skip);
};
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetSerializeContext());
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetBehaviorContext());
PythonReflectionAssetTypes pythonReflectionAssetTypes;
pythonReflectionAssetTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionAssetTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr
import azlmbr.asset
import azlmbr.test
compare_asset_ids = azlmbr.test.PythonReflectionAssetTypes_compare_asset_ids
create_asset_id = azlmbr.test.PythonReflectionAssetTypes_create_asset_id
assetIdOne = create_asset_id('{1F5252DC-467A-4E2E-8168-EE1551C92F74}:0')
assetIdTwo = create_asset_id('{1F5252DC-467A-4E2E-8168-EE1551C92F74}:1')
assetIdThree = azlmbr.asset.AssetId_CreateString('{BA5EBA11-DEAD-AB1E-FACE-01234567890A}:0')
if(assetIdTwo.to_string() == '{1F5252DC-467A-4E2E-8168-EE1551C92F74}:1'):
print ('AssetId: compare_asset_ids assetIdTwo')
if(assetIdThree.to_string() == '{BA5EBA11-DEAD-AB1E-FACE-01234567890A}:0'):
print ('AssetId: compare_asset_ids assetIdThree')
if (compare_asset_ids(assetIdOne, assetIdOne)):
print ('AssetId: compare_asset_ids AFF')
if (compare_asset_ids(assetIdOne, assetIdTwo) is False):
print ('AssetId: compare_asset_ids NEG')
tester = azlmbr.test.PythonReflectionAssetTypes()
tester.assetId = assetIdOne
if (compare_asset_ids(tester.assetId, assetIdOne)):
print ('AssetId: compare_asset_ids tester')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on to run script buffer with %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(5, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::AssetId)]);
}
TEST_F(PythonAssetTypesTests, AssetDataTypes)
{
enum class LogTypes
{
Skip = 0,
AssetData
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "AssetData"))
{
return static_cast<int>(LogTypes::AssetData);
}
}
return static_cast<int>(LogTypes::Skip);
};
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetSerializeContext());
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetBehaviorContext());
PythonReflectionAssetTypes pythonReflectionAssetTypes;
pythonReflectionAssetTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionAssetTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr
import azlmbr.asset
import azlmbr.test
compare_asset_data = azlmbr.test.PythonReflectionAssetTypes_compare_asset_data
print_asset_data = azlmbr.test.PythonReflectionAssetTypes_print_asset_data
generate_asset = azlmbr.test.PythonReflectionAssetTypes_generate_asset
create_asset_id = azlmbr.test.PythonReflectionAssetTypes_create_asset_id
tester = azlmbr.test.PythonReflectionAssetTypes()
# AZ::Data::Asset<> testing
assetIdOne = create_asset_id('{1F5252DC-467A-4E2E-8168-EE1551C92F74}:0')
dataAsset = tester.create_asset_handle(assetIdOne)
print_asset_data(tester.assetData)
print_asset_data(dataAsset)
tester.assetData = dataAsset
mockAsset0 = generate_asset()
mockAsset1 = generate_asset()
if (compare_asset_data(mockAsset1, mockAsset1)):
print ('AssetData: compare_asset_data tester')
# Compare testing
if (compare_asset_data(tester.assetData, dataAsset)):
print ('AssetData: compare_asset_data tester.assetData')
# handling generic Asset<MyTestAssetData>
tester.read_my_test_asset_data(tester.myTestAssetDataAsset)
testAssetData = tester.create_my_test_asset_data()
tester.read_my_test_asset_data(testAssetData)
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on to run script buffer with %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(4, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::AssetData)]);
}
TEST_F(PythonAssetTypesTests, MockAssetTypes)
{
enum class LogTypes
{
Skip = 0,
MockAsset
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "MockAsset"))
{
return static_cast<int>(LogTypes::MockAsset);
}
}
return static_cast<int>(LogTypes::Skip);
};
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetSerializeContext());
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetBehaviorContext());
PythonReflectionAssetTypes pythonReflectionAssetTypes;
pythonReflectionAssetTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionAssetTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr
import azlmbr.asset
import azlmbr.test
compare_mock_assets = azlmbr.test.PythonReflectionAssetTypes_compare_mock_assets
tester0 = azlmbr.test.PythonReflectionAssetTypes()
tester1 = azlmbr.test.PythonReflectionAssetTypes()
if (compare_mock_assets(tester0.mockDescriptor.mockAsset, tester1.mockDescriptor.mockAsset)):
print('MockAsset: mock assets match')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on to run script buffer with %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::MockAsset)]);
}
TEST_F(PythonAssetTypesTests, SimpleAssetReferenceTypes)
{
enum class LogTypes
{
Skip = 0,
SimpleAssetReference
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "SimpleAssetReference"))
{
return static_cast<int>(LogTypes::SimpleAssetReference);
}
}
return static_cast<int>(LogTypes::Skip);
};
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetSerializeContext());
AzFramework::SimpleAssetReferenceBase::Reflect(m_app.GetBehaviorContext());
PythonReflectionAssetTypes pythonReflectionAssetTypes;
pythonReflectionAssetTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionAssetTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr
import azlmbr.asset
import azlmbr.test
create_asset_id = azlmbr.test.PythonReflectionAssetTypes_create_asset_id
print_simple_asset_reference = azlmbr.test.PythonReflectionAssetTypes_print_simple_asset_reference
assetIdOne = create_asset_id('{1F5252DC-467A-4E2E-8168-EE1551C92F74}:0')
assetIdTwo = create_asset_id('{1F5252DC-467A-4E2E-8168-EE1551C92F74}:1')
tester = azlmbr.test.PythonReflectionAssetTypes()
# SimpleAssetReferenceBase basic testing
tester.testAssetId = assetIdOne
accessAssetPath = tester.mockDescriptor.mockAsset.assetPath
print_simple_asset_reference(tester.mockDescriptor.simpleAssetReference)
# SimpleAssetReference<> testing
fakeAssetPath = 'a/fake/asset_file.foo'
mocker = tester.mockDescriptor
simpleAssetReference = mocker.simpleAssetReference
mocker.printFooMockSimpleAsset(simpleAssetReference)
outAssetRef = mocker.createFooMockSimpleAsset(fakeAssetPath)
if(simpleAssetReference.assetPath == fakeAssetPath):
print('SimpleAssetReference: path access matches {}'.format(fakeAssetPath))
# using FooMockSimpleAsset inside a SimpleAssetReference<> template
newFakeAssetPath = 'another/fake/asset_file.foo'
simpleRef = azlmbr.object.construct('SimpleAssetReference<AssetType><FooMockSimpleAsset >')
simpleRef.set_asset_path(newFakeAssetPath)
if(simpleRef.assetPath == newFakeAssetPath):
print('SimpleAssetReference: simpleRef {}'.format(newFakeAssetPath))
if(simpleRef.assetPath is not simpleAssetReference.assetPath):
print('SimpleAssetReference: simpleRef does not match simpleAssetReference')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on to run script buffer with %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(5, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::SimpleAssetReference)]);
}
TEST_F(PythonAssetTypesTests, MockBindingAssetIds)
{
enum class LogTypes
{
Skip = 0,
MockBinding
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "MockBinding"))
{
return static_cast<int>(LogTypes::MockBinding);
}
}
return static_cast<int>(LogTypes::Skip);
};
MockBinding::Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr
import azlmbr.mock
import azlmbr.asset
assetIdStringValue = '{13DACEEC-69B9-4CE4-9F43-50675D73FD8C}:0'
testId = azlmbr.asset.AssetId_CreateString(assetIdStringValue)
if (testId is not None):
print('MockBinding: created mock asset ID')
if (testId.to_string() == assetIdStringValue):
print('MockBinding: created mock asset ID')
testMock = azlmbr.mock.MockBinding(testId)
if (testMock is not None):
print('MockBinding: mock binding created with asset ID')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on to run script buffer with %s", e.what());
}
e.Deactivate();
EXPECT_EQ(3, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::MockBinding)]);
}
TEST_F(PythonAssetTypesTests, AssetIdsEqualOperators)
{
enum class LogTypes
{
Skip = 0,
EqualOperators
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "EqualOperators"))
{
return static_cast<int>(LogTypes::EqualOperators);
}
}
return static_cast<int>(LogTypes::Skip);
};
MockBinding::Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr
import azlmbr.asset
assetIdStringValue0 = '{13DACEEC-69B9-4CE4-9F43-50675D73FD8C}:0'
assetIdStringValue1 = '{13DACEEC-69B9-4CE4-9F43-50675D73FD8C}:1'
testId0 = azlmbr.asset.AssetId_CreateString(assetIdStringValue0)
if (testId0 is not None):
print('EqualOperators: created testId0')
testId1 = azlmbr.asset.AssetId_CreateString(assetIdStringValue1)
if (testId1 is not None):
print('EqualOperators: created testId1')
if (testId1 == azlmbr.asset.AssetId_CreateString(assetIdStringValue1)):
print('EqualOperators: testId1 == testId1')
if (testId0 != testId1):
print('EqualOperators: testId0 != testId1')
if ((testId0 == assetIdStringValue0) is not True):
print('EqualOperators: testId0 != assetIdStringValue0')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on to run script buffer with %s", e.what());
}
e.Deactivate();
EXPECT_EQ(5, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::EqualOperators)]);
}
}
@@ -0,0 +1,259 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
namespace UnitTest
{
struct PythonReflectUnorderedSet
{
AZ_TYPE_INFO(PythonReflectUnorderedSet, "{A596466F-2F29-4479-A721-0E50FA704962}");
AZStd::unordered_set<AZ::u8> m_u8Set {1,2};
AZStd::unordered_set<AZ::u16> m_u16Set {4,8};
AZStd::unordered_set<AZ::u32> m_u32Set {16,32};
AZStd::unordered_set<AZ::u64> m_u64Set {64,128};
AZStd::unordered_set<AZ::s8> m_s8Set {-1,-2};
AZStd::unordered_set<AZ::s16> m_s16Set {-4,-8};
AZStd::unordered_set<AZ::s32> m_s32Set {-16,-32};
AZStd::unordered_set<AZ::s64> m_s64Set {-64,-128};
AZStd::unordered_set<double> m_floatSet {1.0f, 2.0f};
AZStd::unordered_set<float> m_doubleSet {0.1, 0.2};
AZStd::unordered_set<AZStd::string> m_stringSet {"one", "two"};
void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->RegisterGenericType<decltype(m_u8Set)>();
serializeContext->RegisterGenericType<decltype(m_u16Set)>();
serializeContext->RegisterGenericType<decltype(m_u32Set)>();
serializeContext->RegisterGenericType<decltype(m_u64Set)>();
serializeContext->RegisterGenericType<decltype(m_s8Set)>();
serializeContext->RegisterGenericType<decltype(m_s16Set)>();
serializeContext->RegisterGenericType<decltype(m_s32Set)>();
serializeContext->RegisterGenericType<decltype(m_s64Set)>();
serializeContext->RegisterGenericType<decltype(m_floatSet)>();
serializeContext->RegisterGenericType<decltype(m_doubleSet)>();
serializeContext->RegisterGenericType<decltype(m_stringSet)>();
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<PythonReflectUnorderedSet>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test.set")
->Property("u8Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_u8Set) )
->Property("u16Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_u16Set))
->Property("u32Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_u32Set))
->Property("u64Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_u64Set))
->Property("s8Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_s8Set))
->Property("s16Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_s16Set))
->Property("s32Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_s32Set))
->Property("s64Set", BehaviorValueProperty(&PythonReflectUnorderedSet::m_s64Set))
->Property("floatSet", BehaviorValueProperty(&PythonReflectUnorderedSet::m_floatSet))
->Property("doubleSet", BehaviorValueProperty(&PythonReflectUnorderedSet::m_doubleSet))
->Property("stringSet", BehaviorValueProperty(&PythonReflectUnorderedSet::m_stringSet))
;
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonAssociativeTest
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
};
TEST_F(PythonAssociativeTest, SimpleUnorderedSet_Assignment)
{
enum class LogTypes
{
Skip = 0,
Update,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AZ::StringFunc::Equal(window, "python"))
{
if (AZ::StringFunc::StartsWith(message, "Update"))
{
return aznumeric_cast<int>(LogTypes::Update);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
PythonReflectUnorderedSet pythonReflectUnorderedSet;
pythonReflectUnorderedSet.Reflect(m_app.GetSerializeContext());
pythonReflectUnorderedSet.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.set
tester = azlmbr.test.set.PythonReflectUnorderedSet()
def updateNumberDataSet(memberSet, dataSet):
memberSet = dataSet
for value in memberSet:
if (value in dataSet):
print ('Update_worked_{}'.format(memberSet))
updateNumberDataSet(tester.u8Set, {2, 1})
updateNumberDataSet(tester.u16Set, {8, 4})
updateNumberDataSet(tester.u32Set, {32, 16})
updateNumberDataSet(tester.u64Set, {128, 64})
updateNumberDataSet(tester.s8Set, {-2, -1})
updateNumberDataSet(tester.s16Set, {-8, -4})
updateNumberDataSet(tester.s32Set, {-32, -16})
updateNumberDataSet(tester.s64Set, {-128, -64})
from azlmbr.math import Math_IsClose
def updateFloatDataSet(memberFloatSet, dataSet):
memberFloatSet = dataSet
for dataItem in dataSet:
for memberItem in memberFloatSet:
if (Math_IsClose(dataItem, memberItem)):
print ('Update_float_worked_{}'.format(memberFloatSet))
updateFloatDataSet(tester.floatSet, {4.0, 8.0})
updateFloatDataSet(tester.doubleSet, {0.4, 0.8})
stringDataSet = {'three','four'}
tester.stringSet = stringDataSet
for dataItem in stringDataSet:
for memberItem in tester.stringSet:
if (dataItem == memberItem):
print ('Update_string_worked')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed with Python exception of %s", e.what());
}
e.Deactivate();
EXPECT_EQ(22, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Update)]);
}
TEST_F(PythonAssociativeTest, SimpleUnorderedSet_Creation)
{
enum class LogTypes
{
Skip = 0,
Create,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AZ::StringFunc::Equal(window, "python"))
{
if (AZ::StringFunc::StartsWith(message, "Create"))
{
return aznumeric_cast<int>(LogTypes::Create);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
PythonReflectUnorderedSet pythonReflectUnorderedSet;
pythonReflectUnorderedSet.Reflect(m_app.GetSerializeContext());
pythonReflectUnorderedSet.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.set
tester = azlmbr.test.set.PythonReflectUnorderedSet()
if (tester.u8Set == {1, 2}):
print ('Create_Works_u8Set')
if (tester.u16Set == {4, 8}):
print ('Create_Works_u16Set')
if (tester.u32Set == {16, 32}):
print ('Create_Works_u32Set')
if (tester.u64Set == {64, 128}):
print ('Create_Works_u64Set')
if (tester.s8Set == {-1, -2}):
print ('Create_Works_s8Set')
if (tester.s16Set == {-4, -8}):
print ('Create_Works_s16Set')
if (tester.s32Set == {-16, -32}):
print ('Create_Works_s32Set')
if (tester.s64Set == {-64, -128}):
print ('Create_Works_s64Set')
from azlmbr.math import Math_IsClose
for value in tester.floatSet:
if (Math_IsClose(value, 1.0) or Math_IsClose(value, 2.0)):
print ('Create_Works_floatSet')
for value in tester.doubleSet:
if (Math_IsClose(value, 0.1) or Math_IsClose(value, 0.2)):
print ('Create_Works_doubleSet')
for value in tester.stringSet:
if ((value == 'one') or (value == 'two')):
print ('Create_Works_stringSet')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed with Python exception of %s", e.what());
}
e.Deactivate();
EXPECT_EQ(14, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Create)]);
}
}
@@ -0,0 +1,426 @@
/*
* 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.
*
*/
#include <Source/PythonSystemComponent.h>
#include <Source/PythonCommon.h>
#include <Source/PythonTypeCasters.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include <EditorPythonBindings/EditorPythonBindingsBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
// an example converter for an "AZ type"
namespace TestTypes
{
void RegisterAzEntityId(pybind11::module m)
{
auto classEntityId = pybind11::class_<AZ::EntityId>(m, AZ::AzTypeInfo<AZ::EntityId>::Name());
classEntityId.def(pybind11::init<AZ::u64>());
classEntityId.def("isValid", &AZ::EntityId::IsValid);
classEntityId.def("setInvalid", &AZ::EntityId::SetInvalid);
classEntityId.def_property_readonly("id", [](const AZ::EntityId& e) { return static_cast<AZ::u64>(e); });
classEntityId.def("__repr__", &AZ::EntityId::ToString);
}
}
// this is called the first time a Python script "import azlmbrtest"
PYBIND11_EMBEDDED_MODULE(azlmbrtest, m)
{
EditorPythonBindings::EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindings::EditorPythonBindingsNotificationBus::Events::OnImportModule, m.ptr());
TestTypes::RegisterAzEntityId(m);
}
namespace UnitTest
{
struct MyPythonBindings final
: public EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler
{
int m_onImportModuleCount = 0;
MyPythonBindings()
{
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusConnect();
}
~MyPythonBindings()
{
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
}
static long DoAdd(int lhs, int rhs)
{
return lhs + rhs;
}
static void AZPrintf([[maybe_unused]] const AZStd::string& message)
{
AZ_TracePrintf("python", "%s", message.c_str());
}
void ImportTestSubModule(pybind11::module module)
{
pybind11::module subModule = module.def_submodule("tester", "A submodule for 'test'");
subModule.def("add", &DoAdd);
subModule.def("print", &AZPrintf);
}
void OnImportModule(PyObject* module) override
{
pybind11::module m = pybind11::cast<pybind11::module>(module);
std::string szName = pybind11::cast<std::string>(m.attr("__name__"));
if (szName == "azlmbrtest")
{
m_onImportModuleCount++;
ImportTestSubModule(m);
}
}
};
class PythonBindingLibTest
: public PythonTestingFixture
{
protected:
void SetUp() override
{
PythonTestingFixture::SetUp();
RegisterComponentDescriptors();
}
void TearDown() override
{
PythonTestingFixture::TearDown();
}
};
TEST_F(PythonBindingLibTest, ImportBaseModule)
{
AZ::Entity entity;
entity.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
entity.Init();
entity.Activate();
SimulateEditorBecomingInitialized();
{
MyPythonBindings pythonBindings;
pybind11::module::import("azlmbrtest");
EXPECT_EQ(pythonBindings.m_onImportModuleCount, 1);
}
entity.Deactivate();
}
TEST_F(PythonBindingLibTest, ImportBaseModuleTwice)
{
const char* script =
R"(
import azlmbrtest
import azlmbrtest
)";
AZ::Entity entity;
entity.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
entity.Init();
entity.Activate();
SimulateEditorBecomingInitialized();
// Python keeps track of the module import count so that multiple attempts should result into a single import count
{
MyPythonBindings pythonBindings;
EXPECT_EQ(PyRun_SimpleString(script), 0);
EXPECT_EQ(pythonBindings.m_onImportModuleCount, 1);
}
entity.Deactivate();
}
TEST_F(PythonBindingLibTest, ExecuteSimpleBinding)
{
enum class LogTypes
{
Skip = 0,
TesterAdd,
TesterPrinted
};
PythonTraceMessageSink testSink;
testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
AZStd::string_view w(window);
if (w == "python")
{
AZStd::string_view m(message);
if (m == "tester add equals 42")
{
return (int)LogTypes::TesterAdd;
}
if (m == "tester says yo")
{
return (int)LogTypes::TesterPrinted;
}
}
return (int)LogTypes::Skip;
};
const char* script =
R"(
import azlmbrtest
value = azlmbrtest.tester.add(40, 2)
print ('tester add equals ' + str(value))
value = azlmbrtest.tester.print('tester says yo')
)";
AZ::Entity entity;
entity.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
entity.Init();
entity.Activate();
SimulateEditorBecomingInitialized();
{
MyPythonBindings pythonBindings;
EXPECT_EQ(PyRun_SimpleString(script), 0);
EXPECT_EQ(pythonBindings.m_onImportModuleCount, 1);
EXPECT_EQ(testSink.m_evaluationMap[(int)LogTypes::TesterAdd], 1);
EXPECT_EQ(testSink.m_evaluationMap[(int)LogTypes::TesterPrinted], 1);
}
entity.Deactivate();
}
TEST_F(PythonBindingLibTest, ConvertAZTypes)
{
enum class LogTypes
{
Skip = 0,
TypeConverted,
IdIsValid,
IdHasRepr,
IdNowInvalid
};
PythonTraceMessageSink testSink;
testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
AZStd::string_view m(message);
AZStd::string_view w(window);
if (w == "python")
{
if (m == "entityId equals 10")
{
return (int)LogTypes::TypeConverted;
}
else if (m == "entityId is valid True")
{
return (int)LogTypes::IdIsValid;
}
else if (m == "entityId is repr [10]")
{
return (int)LogTypes::IdHasRepr;
}
else if (m == "entityId invalid is 4294967295")
{
return (int)LogTypes::IdNowInvalid;
}
}
return (int)LogTypes::Skip;
};
const char* script =
R"(
import azlmbrtest
entityId = azlmbrtest.EntityId(10)
print ('entityId equals ' + str(entityId.id))
print ('entityId is valid ' + str(entityId.isValid()))
print ('entityId is repr ' + str(entityId))
entityId.setInvalid()
print ('entityId invalid is ' + str(entityId.id))
)";
AZ::Entity entity;
entity.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
entity.Init();
entity.Activate();
SimulateEditorBecomingInitialized();
EXPECT_EQ(PyRun_SimpleString(script), 0);
EXPECT_EQ(testSink.m_evaluationMap[(int)LogTypes::TypeConverted], 1);
EXPECT_EQ(testSink.m_evaluationMap[(int)LogTypes::IdIsValid], 1);
EXPECT_EQ(testSink.m_evaluationMap[(int)LogTypes::IdHasRepr], 1);
EXPECT_EQ(testSink.m_evaluationMap[(int)LogTypes::IdNowInvalid], 1);
entity.Deactivate();
}
TEST_F(PythonBindingLibTest, ImportProjectModules)
{
enum class LogTypes
{
Skip = 0,
ImportModule,
TestCallHit,
TestTypeDoCall1
};
PythonTraceMessageSink testSink;
testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "ImportModule"))
{
return static_cast<int>(LogTypes::ImportModule);
}
else if (AzFramework::StringFunc::Equal(message, "test_call_hit"))
{
return static_cast<int>(LogTypes::TestCallHit);
}
else if (AzFramework::StringFunc::Equal(message, "TestType.do_call.1"))
{
return static_cast<int>(LogTypes::TestTypeDoCall1);
}
}
return static_cast<int>(LogTypes::Skip);
};
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import sys, os
import azlmbr.paths
sys.path.append(os.path.join(azlmbr.paths.devroot,'Gems','EditorPythonBindings','Code','Tests'))
from test_package import import_test as itest
print('ImportModule')
itest.test_call()
testInst = itest.TestType()
testInst.do_call(1)
)");
}
catch ([[maybe_unused]] const std::exception& exception)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", exception.what());
}
e.Deactivate();
EXPECT_EQ(1, testSink.m_evaluationMap[static_cast<int>(LogTypes::ImportModule)]);
EXPECT_EQ(1, testSink.m_evaluationMap[static_cast<int>(LogTypes::TestCallHit)]);
EXPECT_EQ(1, testSink.m_evaluationMap[static_cast<int>(LogTypes::TestTypeDoCall1)]);
}
TEST_F(PythonBindingLibTest, PyDocHelp_AzlmbrGlobals_Works)
{
enum class LogTypes
{
Skip = 0,
Worked
};
PythonTraceMessageSink testSink;
testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "Worked"))
{
return aznumeric_cast<int>(LogTypes::Worked);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import pydoc
import azlmbr.globals
pydoc.help(azlmbr.globals)
print('Worked')
)");
}
catch ([[maybe_unused]] const std::exception& exception)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", exception.what());
}
e.Deactivate();
EXPECT_EQ(1, testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Worked)]);
}
TEST_F(PythonBindingLibTest, ImportAzLmbrTwice)
{
enum class LogTypes
{
Skip = 0,
ImportAzLmbrTwice,
SawEntityId
};
PythonTraceMessageSink testSink;
testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "ImportAzLmbrTwice"))
{
return aznumeric_cast<int>(LogTypes::ImportAzLmbrTwice);
}
else if (AzFramework::StringFunc::StartsWith(message, "entity_id 101"))
{
return aznumeric_cast<int>(LogTypes::SawEntityId);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import sys, os
import azlmbr.paths
sys.path.append(os.path.join(azlmbr.paths.devroot,'Gems','EditorPythonBindings','Code','Tests'))
sys.path.append(os.path.join(azlmbr.paths.devroot,'Gems','EditorPythonBindings','Code','Tests','test_package'))
from test_package import import_many
import_many.test_many_entity_id()
print('ImportAzLmbrTwice')
)");
}
catch ([[maybe_unused]] const std::exception& exception)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", exception.what());
}
e.Deactivate();
EXPECT_EQ(1, testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::ImportAzLmbrTwice)]);
EXPECT_EQ(1, testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::SawEntityId)]);
}
}
@@ -0,0 +1,481 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <AzCore/PlatformDef.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
#include <Source/PythonProxyObject.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace CustomTest
{
template <typename T>
struct MyTemplate
{
MyTemplate() = default;
MyTemplate(T value) : m_value(value) {}
T m_value = {};
};
}
namespace AZ
{
template<typename T>
struct OnDemandReflection<CustomTest::MyTemplate<T>>
{
using MyTemplateType = CustomTest::MyTemplate<T>;
static void Reflect(ReflectContext* context)
{
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->Class<MyTemplateType>()
->Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Automation)
->Attribute(Script::Attributes::Module, "test.template")
->Property("Value",
[] (MyTemplateType* that) -> T { return that->m_value; },
[] (MyTemplateType* that, const T& value) { that->m_value = value; })
;
}
}
};
AZ_TYPE_INFO_TEMPLATE(CustomTest::MyTemplate, "{82B9D060-F077-4FAA-9EF4-EF4C3A2A6332}", AZ_TYPE_INFO_CLASS);
}
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// test class/struts
struct CustomTypeHolder
{
AZ_TYPE_INFO(MyTemplate, "{46543B40-D8AF-4498-BCD0-2FF2A040B42C}");
CustomTest::MyTemplate<float> m_testFloat;
CustomTest::MyTemplate<AZStd::string> m_testString;
CustomTest::MyTemplate<int> m_testInt;
CustomTypeHolder()
: m_testFloat(42.0f)
, m_testString("42")
, m_testInt(42)
{
}
void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->RegisterGenericType<CustomTest::MyTemplate<float>>();
serializeContext->RegisterGenericType<CustomTest::MyTemplate<AZStd::string>>();
serializeContext->RegisterGenericType<CustomTest::MyTemplate<int>>();
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<CustomTypeHolder>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Method("set_float", [](CustomTypeHolder& self, float value) { self.m_testFloat.m_value = value; })
->Property("test_float", BehaviorValueProperty(&CustomTypeHolder::m_testFloat))
->Property("test_string", BehaviorValueProperty(&CustomTypeHolder::m_testString))
->Property("test_int", BehaviorValueProperty(&CustomTypeHolder::m_testInt))
;
}
}
};
struct Descriptor final
{
AZ_TYPE_INFO(Descriptor, "{0DFEE628-EFE2-4B9B-BAF2-40ED2965E663}");
void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->RegisterGenericType<Descriptor>();
serializeContext->RegisterGenericType<AZStd::vector<Descriptor>>();
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<Descriptor>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Property("s32", BehaviorValueProperty(&Descriptor::m_s32))
->Property("u32", BehaviorValueProperty(&Descriptor::m_u32))
->Property("scalar", BehaviorValueProperty(&Descriptor::m_scalar))
->Property("bool_value", BehaviorValueProperty(&Descriptor::m_bool))
->Property("string_value", BehaviorValueProperty(&Descriptor::m_stringValue))
->Method("return_dummy_descriptor", []() { static Descriptor dummy; return dummy; }, nullptr, "")
->Method("return_dummy_vector_descriptor", []() { static AZStd::vector<Descriptor> dummy; return dummy; }, nullptr, "")
;
}
}
Descriptor() = default;
~Descriptor() = default;
AZ::s32 m_s32 = -1234;
AZ::u32 m_u32 = 0xDEADBEEF;
float m_scalar = -456.0f;
bool m_bool = true;
AZStd::string m_stringValue;
};
struct PythonReflectionAnyContainer
{
AZ_TYPE_INFO(PythonReflectionAnyContainer, "{D7D45479-9A46-469E-BE75-F305EBE8F848}");
AZStd::any m_anyList; // will store a container like vector
PythonReflectionAnyContainer()
{
AZStd::vector<AZ::s64> numbers{ 1,2,3,5,8,13 };
m_anyList = AZStd::make_any<AZStd::vector<AZ::s64>>(numbers);
}
void MutateAnyContainer(const AZStd::any& value)
{
m_anyList = value;
if(m_anyList.is<AZStd::vector<Descriptor>>())
{
const AZStd::vector<Descriptor>* ptr = AZStd::any_cast<AZStd::vector<Descriptor>>(&m_anyList);
if (!ptr->empty())
{
AZ_Printf("python", "ReplaceAnyList_AZStd::vector<Descriptor>", ptr->size());
}
}
}
const AZStd::any& AccessAnyContainer() const
{
if (m_anyList.is<AZStd::vector<Descriptor>>())
{
const AZStd::vector<Descriptor>* ptr = AZStd::any_cast<AZStd::vector<Descriptor>>(&m_anyList);
if (!ptr->empty())
{
AZ_Printf("python", "AccessAnyList_AZStd::vector<Descriptor>", ptr->size());
}
}
return m_anyList;
}
void Reflect(AZ::ReflectContext* context)
{
using namespace EditorPythonBindings;
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->RegisterGenericType<AZStd::vector<AZStd::any>>();
serializeContext->RegisterGenericType<AZStd::vector<AZ::s64>>();
serializeContext->RegisterGenericType<AZStd::vector<double>>();
serializeContext->RegisterGenericType<AZStd::vector<bool>>();
serializeContext->RegisterGenericType<AZStd::vector<AZStd::string>>();
serializeContext->RegisterGenericType<AZStd::vector<PythonProxyObject>>();
;
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<PythonReflectionAnyContainer>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Method("mutate_any_container", &PythonReflectionAnyContainer::MutateAnyContainer, nullptr, "")
->Method("access_any_container", &PythonReflectionAnyContainer::AccessAnyContainer, nullptr, "")
->Method("return_dummy_vector_integer", []() { static AZStd::vector<AZ::s64> dummy; return dummy; }, nullptr, "")
->Method("return_dummy_vector_double", []() { static AZStd::vector<double> dummy; return dummy; }, nullptr, "")
->Method("return_dummy_vector_bool", []() { static AZStd::vector<bool> dummy; return dummy; }, nullptr, "")
->Method("return_dummy_vector_string", []() { static AZStd::vector<AZStd::string> dummy; return dummy; }, nullptr, "")
->Method("return_dummy_vector_proxy", []() { static AZStd::vector<PythonProxyObject> dummy; return dummy; }, nullptr, "")
;
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonReflectAnyContainerTests
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
};
TEST_F(PythonReflectAnyContainerTests, AccessReplaceVectorTypes)
{
enum class LogTypes
{
Skip = 0,
AccessAnyList,
ReplaceAnyList,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "AccessAnyList"))
{
return aznumeric_cast<int>(LogTypes::AccessAnyList);
}
else if (AzFramework::StringFunc::StartsWith(message, "ReplaceAnyList"))
{
return aznumeric_cast<int>(LogTypes::ReplaceAnyList);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
PythonReflectionAnyContainer pythonReflectionAnyContainer;
pythonReflectionAnyContainer.Reflect(m_app.GetBehaviorContext());
pythonReflectionAnyContainer.Reflect(m_app.GetSerializeContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test as test
testObject = test.PythonReflectionAnyContainer()
target = [1,2,3,5,8,13]
values = testObject.access_any_container()
if (len(values) > 0):
print ('AccessAnyList_for_values')
if (values == target):
print ('AccessAnyList_matching_ends')
target.reverse()
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if (values == target):
print ('ReplaceAnyList_replaced_as_reversed')
target = [True,False,True,True]
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if( type(values[0]) is bool):
print ('AccessAnyList_matching_bools')
target.reverse()
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if (values == target):
print ('ReplaceAnyList_replaced_bools')
target = [-1.0,1.0,-10.0,10.0]
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if (values == target):
print ('AccessAnyList_matching_floats')
target.reverse()
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if (values == target):
print ('ReplaceAnyList_replaced_floats')
target = ['one','2','three','0x4']
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if (values == target):
print ('AccessAnyList_matching_strings')
target.reverse()
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if (values == target):
print ('ReplaceAnyList_strings')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed with %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(5, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::AccessAnyList)]);
EXPECT_EQ(4, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::ReplaceAnyList)]);
}
TEST_F(PythonReflectAnyContainerTests, AccessReplaceComplexTypes)
{
enum class LogTypes
{
Skip = 0,
AccessAnyList,
ReplaceAnyList,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "AccessAnyList"))
{
return aznumeric_cast<int>(LogTypes::AccessAnyList);
}
else if (AzFramework::StringFunc::StartsWith(message, "ReplaceAnyList"))
{
return aznumeric_cast<int>(LogTypes::ReplaceAnyList);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
PythonReflectionAnyContainer pythonReflectionAnyContainer;
pythonReflectionAnyContainer.Reflect(m_app.GetBehaviorContext());
pythonReflectionAnyContainer.Reflect(m_app.GetSerializeContext());
Descriptor descriptor;
descriptor.Reflect(m_app.GetBehaviorContext());
descriptor.Reflect(m_app.GetSerializeContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test as test
import azlmbr.object
testObject = test.PythonReflectionAnyContainer()
def create_descriptor(s32, u32, scalar, bool_value, string_value):
descriptor = test.Descriptor()
descriptor.s32 = s32
descriptor.u32 = u32
descriptor.scalar = scalar
descriptor.bool_value = bool_value
descriptor.string_value = string_value
return descriptor
def equals_descriptor(lhs, rhs):
return (lhs.s32 == rhs.s32 and
lhs.u32 == rhs.u32 and
lhs.scalar == rhs.scalar and
lhs.bool_value == rhs.bool_value and
lhs.string_value == rhs.string_value)
target = []
target.append(create_descriptor(-1, 2, 3.0, True, 'one'))
target.append(create_descriptor(-2, 3, 4.0, False, '0X2'))
target.append(create_descriptor(-3, 4, 5.0, True, 'T H R E E'))
testObject.mutate_any_container(target)
values = testObject.access_any_container()
if( isinstance(values[0], azlmbr.object.PythonProxyObject) and values[0].typename == 'Descriptor'):
print ('AccessAnyList_matches_descriptor_type')
target.reverse()
testObject.mutate_any_container(target)
values = testObject.access_any_container()
for x in range(0, len(values)):
if ( equals_descriptor(values[x], target[x]) ):
print ('ReplaceAnyList_replaced_descriptors')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed with %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(3, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::AccessAnyList)]);
EXPECT_EQ(5, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::ReplaceAnyList)]);
}
TEST_F(PythonReflectAnyContainerTests, CustomTypeTemplates)
{
enum class LogTypes
{
Skip = 0,
Float,
String,
Integer
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "Float"))
{
return aznumeric_cast<int>(LogTypes::Float);
}
else if (AzFramework::StringFunc::StartsWith(message, "String"))
{
return aznumeric_cast<int>(LogTypes::String);
}
else if (AzFramework::StringFunc::StartsWith(message, "Integer"))
{
return aznumeric_cast<int>(LogTypes::Integer);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
CustomTypeHolder customTypeHolder;
customTypeHolder.Reflect(m_app.GetBehaviorContext());
customTypeHolder.Reflect(m_app.GetSerializeContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test
import azlmbr.test.template
templateFloat = azlmbr.test.template.CustomTest_MyTemplate_float(40.0 + 2.0)
print('Float - created template with float')
templateString = azlmbr.test.template.CustomTest_MyTemplate_string('forty-two')
print('String - created template with string')
templateInt = azlmbr.test.template.CustomTest_MyTemplate_int(40 + 2)
print('Integer - created template with int')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed with %s", e.what());
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Float)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::String)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Integer)]);
}
}
@@ -0,0 +1,340 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest
{
struct PythonReflectionDictionaryTypes final
{
AZ_TYPE_INFO(PythonReflectionDictionaryTypes, "{478AD363-467D-4285-BE40-4D1CB1A09A19}");
template <typename K, typename V>
struct MapOf
{
using MapType = AZStd::unordered_map<K,V>;
MapType m_map;
explicit MapOf(const std::initializer_list<AZStd::pair<K, V>> map)
{
m_map = map;
}
const MapType& ReturnMap() const
{
return m_map;
}
void AcceptMap(const MapType& other)
{
m_map = other;
}
void RegisterGenericType(AZ::SerializeContext& serializeContext)
{
serializeContext.RegisterGenericType<AZStd::unordered_map<K,V>>();
}
};
MapOf<AZ::u8, AZ::u32> m_indexOfu8tou32 { {1, 4}, {2, 5}, {3, 6}, {4, 7} };
MapOf<AZ::u16, float> m_indexOfu16toFloat { {1, 0.4f}, {2, 0.5f}, {3, 0.6f}, {4, 0.7f} };
MapOf<AZStd::string, AZ::s32> m_indexOfStringTos32 { {"1", -4}, {"2", 5}, {"3", -6}, {"4", 7} };
MapOf<AZStd::string, AZStd::string> m_indexOfStringToString { {"hello", "foo"}, {"world", "bar"}, {"bye", "baz"}, {"sky", "qux"} };
MapOf<AZStd::string, AZ::Vector3> m_indexOfStringToVec3{ {"up", AZ::Vector3{ 0, 1.0, 0 }}, {"down", AZ::Vector3{0, -1.0, 0}},
{"left", AZ::Vector3{1.0, 0, 0}}, {"right", AZ::Vector3{-1, 0, 0}} };
void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
m_indexOfu8tou32.RegisterGenericType(*serializeContext);
m_indexOfu16toFloat.RegisterGenericType(*serializeContext);
m_indexOfStringTos32.RegisterGenericType(*serializeContext);
m_indexOfStringToString.RegisterGenericType(*serializeContext);
m_indexOfStringToVec3.RegisterGenericType(*serializeContext);
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<PythonReflectionDictionaryTypes>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test.dictionary")
->Method("return_dict_of_u8u32", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfu8tou32.ReturnMap(); }, nullptr, "")
->Method("accept_dict_of_u8u32", [](PythonReflectionDictionaryTypes* self, const MapOf<AZ::u8, AZ::u32>::MapType& map) { self->m_indexOfu8tou32.AcceptMap(map); }, nullptr, "")
->Method("return_dict_of_u16toFloat", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfu16toFloat.ReturnMap(); }, nullptr, "")
->Method("accept_dict_of_u16toFloat", [](PythonReflectionDictionaryTypes* self, const MapOf<AZ::u16, float>::MapType& map) { self->m_indexOfu16toFloat.AcceptMap(map); }, nullptr, "")
->Method("return_dict_of_stringTos32", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfStringTos32.ReturnMap(); }, nullptr, "")
->Method("accept_dict_of_stringTos32", [](PythonReflectionDictionaryTypes* self, const MapOf<AZStd::string, AZ::s32>::MapType& map) { self->m_indexOfStringTos32.AcceptMap(map); }, nullptr, "")
->Method("return_dict_of_stringToString", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfStringToString.ReturnMap(); }, nullptr, "")
->Method("accept_dict_of_stringToString", [](PythonReflectionDictionaryTypes* self, const MapOf<AZStd::string, AZStd::string>::MapType& map) { self->m_indexOfStringToString.AcceptMap(map); }, nullptr, "")
->Method("return_dict_of_stringToVec3", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfStringToVec3.ReturnMap(); }, nullptr, "")
->Method("accept_dict_of_stringToVec3", [](PythonReflectionDictionaryTypes* self, const MapOf<AZStd::string, AZ::Vector3>::MapType& map) { self->m_indexOfStringToVec3.AcceptMap(map); }, nullptr, "")
;
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonReflectionDictionaryTests
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
};
TEST_F(PythonReflectionDictionaryTests, InstallingPythonDictionaries)
{
AZ::Entity e;
Activate(e);
EXPECT_EQ(AZ::Entity::State::Active, e.GetState());
SimulateEditorBecomingInitialized();
e.Deactivate();
}
TEST_F(PythonReflectionDictionaryTests, MapSimpleTypes)
{
enum class LogTypes
{
Skip = 0,
ContainerTypes_Input,
ContainerTypes_Output,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Input"))
{
return static_cast<int>(LogTypes::ContainerTypes_Input);
}
else if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Output"))
{
return static_cast<int>(LogTypes::ContainerTypes_Output);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonReflectionDictionaryTypes pythonReflectionDictionaryTypes;
pythonReflectionDictionaryTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionDictionaryTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.dictionary
import azlmbr.object
test = azlmbr.object.create('PythonReflectionDictionaryTypes')
result = test.return_dict_of_u8u32()
if (len(result.items()) == 4):
print ('ContainerTypes_Output_u8u32')
test.accept_dict_of_u8u32({4: 1, 3: 2})
result = test.return_dict_of_u8u32()
if (len(result.items()) == 2):
print ('ContainerTypes_Input_u8u32')
result = test.return_dict_of_u16toFloat()
if (len(result.items()) == 4):
print ('ContainerTypes_Output_u16toFloat')
test.accept_dict_of_u16toFloat({4: 0.1, 3: 0.2})
result = test.return_dict_of_u16toFloat()
if (len(result.items()) == 2):
print ('ContainerTypes_Input_u16toFloat')
result = test.return_dict_of_stringTos32()
if (len(result.items()) == 4):
print ('ContainerTypes_Output_stringTos32')
test.accept_dict_of_stringTos32({'4': -1, '3': 2})
result = test.return_dict_of_stringTos32()
if (len(result.items()) == 2):
print ('ContainerTypes_Input_stringTos32')
result = test.return_dict_of_stringToString()
if (len(result.items()) == 4):
print ('ContainerTypes_Output_stringToString')
test.accept_dict_of_stringToString({'one': '1', 'two': '2'})
result = test.return_dict_of_stringToString()
if (len(result.items()) == 2):
print ('ContainerTypes_Input_stringToString')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed with Python exception of %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(4, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Input)]);
EXPECT_EQ(4, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Output)]);
}
TEST_F(PythonReflectionDictionaryTests, MapTypes_Mismatch_Detected)
{
enum class LogTypes
{
Skip = 0,
Detection,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
constexpr AZStd::string_view warningTypeMismatch =
"Could not convert to pair element type value2 for the pair<>; failed to marshal Python input <class 'int'>";
constexpr AZStd::string_view warningSizeMismatch =
"Python Dict size:2 does not match the size of the unordered_map:0";
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, warningTypeMismatch))
{
return aznumeric_cast<int>(LogTypes::Detection);
}
else if (AzFramework::StringFunc::StartsWith(message, warningSizeMismatch))
{
return aznumeric_cast<int>(LogTypes::Detection);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
PythonReflectionDictionaryTypes pythonReflectionDictionaryTypes;
pythonReflectionDictionaryTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionDictionaryTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.dictionary
import azlmbr.object
test = azlmbr.object.create('PythonReflectionDictionaryTypes')
mismatchMap = {'one': 1, 'two': 2}
test.accept_dict_of_stringToString(mismatchMap)
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed with Python exception of %s", e.what());
}
e.Deactivate();
EXPECT_EQ(3, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Detection)]);
}
TEST_F(PythonReflectionDictionaryTests, MapComplexTypes)
{
enum class LogTypes
{
Skip = 0,
ContainerTypes_Input,
ContainerTypes_Output,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Input"))
{
return static_cast<int>(LogTypes::ContainerTypes_Input);
}
else if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Output"))
{
return static_cast<int>(LogTypes::ContainerTypes_Output);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonReflectionDictionaryTypes pythonReflectionDictionaryTypes;
pythonReflectionDictionaryTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionDictionaryTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.dictionary
import azlmbr.object
test = azlmbr.object.create('PythonReflectionDictionaryTypes')
result = test.return_dict_of_stringToVec3()
if (len(result.items()) == 4):
print ('ContainerTypes_Output_stringToVec3')
vec3dict = {}
vec3dict['120'] = azlmbr.math.Vector3(1.0, -2.0, 0.0)
vec3dict['456'] = azlmbr.math.Vector3(0.4, 0.5, 0.6)
test.accept_dict_of_stringToVec3(vec3dict)
result = test.return_dict_of_stringToVec3()
if (len(result.items()) == 2):
if (result['120'].x > 0 and result['120'].y < 0 and result['120'].z == 0):
print ('ContainerTypes_Input_stringToVec3')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed with Python exception of %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Input)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Output)]);
}
}
@@ -0,0 +1,677 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonProxyObject.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest
{
void AcceptTwoStrings(AZStd::string stringValue1, AZStd::string stringValue2)
{
AZ_TracePrintf("python", stringValue1.empty() ? "stringValue1_is_empty" : "stringValue1_has_data");
AZ_TracePrintf("python", stringValue2.empty() ? "stringValue2_is_empty" : "stringValue2_has_data");
}
//////////////////////////////////////////////////////////////////////////
// test class/struts
struct PythonGlobalsTester
{
AZ_TYPE_INFO(PythonGlobalsTester, "{00EC83FE-2E9D-42D0-8A59-2940669C7BCA}");
enum GlobalEnums : AZ::u16
{
GE_NONE,
GE_LUMBER = 101,
GE_YARD
};
enum class MyTypes
{
One = 1,
Two = 2,
};
static AZ::s32 s_staticValue;
static AZ::u32 s_pingCount;
static GlobalEnums s_result1;
static GlobalEnums s_result2;
static constexpr AZ::u8 s_one = 1;
static AZ::Uuid s_myTypeId;
static AZStd::string s_myString;
static AZ::s32 GetValue()
{
return s_staticValue;
}
static void SetValue(AZ::s32 value)
{
s_staticValue = value;
}
static AZ::u32 Ping()
{
++s_pingCount;
return s_pingCount;
}
static void Reset()
{
s_pingCount = 0;
s_staticValue = 0;
s_result1 = GlobalEnums::GE_NONE;
s_result2 = GlobalEnums::GE_NONE;
s_myTypeId = AZ::TypeId::CreateString("{DEADBEE5-F983-4153-848A-EE9F99502811}");
s_myString = AZStd::string("my string");
}
void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
// Methods
behaviorContext->Method("ping", &PythonGlobalsTester::Ping)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test.pinger");
behaviorContext->Method("reset", &PythonGlobalsTester::Reset)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
behaviorContext->Method("accept_two_strings", AcceptTwoStrings)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
// Property
behaviorContext->Property("constantNumber", []() { return PythonGlobalsTester::GetValue(); }, nullptr)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
behaviorContext->Property("coolProperty", &PythonGlobalsTester::GetValue, &PythonGlobalsTester::SetValue)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
behaviorContext->Property("pingCount", BehaviorValueGetter(&s_pingCount), BehaviorValueSetter(&s_pingCount))
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
// Enums
behaviorContext->EnumProperty<GlobalEnums::GE_LUMBER>("GE_LUMBER")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
behaviorContext->EnumProperty<GlobalEnums::GE_YARD>("GE_YARD")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
// azlmbr.my.enum.One
behaviorContext->EnumProperty<aznumeric_cast<int>(MyTypes::One)>("One")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "my.enum")
;
// azlmbr.my.enum.Two
behaviorContext->EnumProperty<aznumeric_cast<int>(MyTypes::Two)>("Two")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "my.enum")
;
behaviorContext->Property("result1", []() { return s_result1; }, [](GlobalEnums value) { s_result1 = value; })
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
behaviorContext->Property("result2", []() { return s_result2; }, [](GlobalEnums value) { s_result2 = value; })
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
// Constants
behaviorContext->ConstantProperty("ONE", []() { return s_one; })
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
// azlmbr.constant.MY_TYPE
behaviorContext->ConstantProperty("MY_TYPE", []() { return s_myTypeId; })
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "constant")
;
// azlmbr.constant.MY_STRING
behaviorContext->ConstantProperty("MY_STRING", []() { return s_myString; })
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "constant")
;
}
}
};
AZ::s32 PythonGlobalsTester::s_staticValue = 0;
AZ::u32 PythonGlobalsTester::s_pingCount = 0;
PythonGlobalsTester::GlobalEnums PythonGlobalsTester::s_result1 = PythonGlobalsTester::GlobalEnums::GE_NONE;
PythonGlobalsTester::GlobalEnums PythonGlobalsTester::s_result2 = PythonGlobalsTester::GlobalEnums::GE_NONE;
AZ::Uuid PythonGlobalsTester::s_myTypeId;
AZStd::string PythonGlobalsTester::s_myString;
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonGlobalsTests
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
void Deactivate(AZ::Entity& entity)
{
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
if (editorPythonEventsInterface)
{
editorPythonEventsInterface->StopPython();
}
entity.Deactivate();
}
};
//////////////////////////////////////////////////////////////////////////
// tests
TEST_F(PythonGlobalsTests, GlobalMethodTest)
{
PythonGlobalsTester pythonGlobalsTester;
pythonGlobalsTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
# testing global methods
import azlmbr.globals
import azlmbr.test.pinger
azlmbr.globals.reset()
for i in range(830):
azlmbr.test.pinger.ping()
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(830, PythonGlobalsTester::s_pingCount);
}
TEST_F(PythonGlobalsTests, GlobalPropertyTest)
{
enum class LogTypes
{
Skip = 0,
GlobalPropertyTest_NotNone,
GlobalPropertyTest_Is40,
GlobalPropertyTest_Is42,
GlobalPropertyTest_PingWorked,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "GlobalPropertyTest_NotNone"))
{
return static_cast<int>(LogTypes::GlobalPropertyTest_NotNone);
}
else if (AzFramework::StringFunc::Equal(message, "GlobalPropertyTest_Is40"))
{
return static_cast<int>(LogTypes::GlobalPropertyTest_Is40);
}
else if (AzFramework::StringFunc::Equal(message, "GlobalPropertyTest_Is42"))
{
return static_cast<int>(LogTypes::GlobalPropertyTest_Is42);
}
else if (AzFramework::StringFunc::Equal(message, "GlobalPropertyTest_PingWorked"))
{
return static_cast<int>(LogTypes::GlobalPropertyTest_PingWorked);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonGlobalsTester pythonGlobalsTester;
pythonGlobalsTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.globals
import azlmbr.test.pinger
# testing global properties
if (azlmbr.globals.property.constantNumber == 0):
print ('GlobalPropertyTest_NotNone')
azlmbr.globals.property.coolProperty = 40
if (azlmbr.globals.property.coolProperty == 40):
print ('GlobalPropertyTest_Is40')
azlmbr.globals.property.coolProperty = azlmbr.globals.property.coolProperty + 2
if (azlmbr.globals.property.constantNumber == 42):
print ('GlobalPropertyTest_Is42')
azlmbr.globals.property.pingCount = 0
for i in range(830):
azlmbr.test.pinger.ping()
if (azlmbr.globals.property.pingCount == 830):
print ('GlobalPropertyTest_PingWorked')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalPropertyTest_NotNone)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalPropertyTest_Is40)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalPropertyTest_Is42)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalPropertyTest_PingWorked)]);
}
TEST_F(PythonGlobalsTests, GlobalEnumTest)
{
enum class LogTypes
{
Skip = 0,
GlobalEnumTest_Lumber,
GlobalEnumTest_Yard
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "GlobalEnumTest_Lumber"))
{
return static_cast<int>(LogTypes::GlobalEnumTest_Lumber);
}
else if (AzFramework::StringFunc::Equal(message, "GlobalEnumTest_Yard"))
{
return static_cast<int>(LogTypes::GlobalEnumTest_Yard);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonGlobalsTester pythonGlobalsTester;
pythonGlobalsTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.globals
azlmbr.globals.reset()
# testing global enum constant values
if (azlmbr.globals.property.GE_LUMBER == 101):
print ('GlobalEnumTest_Lumber')
if (azlmbr.globals.property.GE_YARD == 102):
print ('GlobalEnumTest_Yard')
azlmbr.globals.property.result1 = azlmbr.globals.property.GE_LUMBER
azlmbr.globals.property.result2 = azlmbr.globals.property.GE_YARD
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalEnumTest_Lumber)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalEnumTest_Yard)]);
EXPECT_EQ(PythonGlobalsTester::GlobalEnums::GE_LUMBER, PythonGlobalsTester::s_result1);
EXPECT_EQ(PythonGlobalsTester::GlobalEnums::GE_YARD, PythonGlobalsTester::s_result2);
}
TEST_F(PythonGlobalsTests, GlobalConstantTest)
{
enum class LogTypes
{
Skip = 0,
GlobalConstantTest_Fetch,
GlobalConstantTest_Adds
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "GlobalConstantTest_Fetch"))
{
return static_cast<int>(LogTypes::GlobalConstantTest_Fetch);
}
else if (AzFramework::StringFunc::Equal(message, "GlobalConstantTest_Adds"))
{
return static_cast<int>(LogTypes::GlobalConstantTest_Adds);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonGlobalsTester pythonGlobalsTester;
pythonGlobalsTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.globals
azlmbr.globals.reset()
# testing global enum constant values
if (azlmbr.globals.property.ONE == 1):
print ('GlobalConstantTest_Fetch')
a = azlmbr.globals.property.ONE
b = azlmbr.globals.property.ONE
if ((a + b) == 2):
print ('GlobalConstantTest_Adds')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalConstantTest_Fetch)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::GlobalConstantTest_Adds)]);
}
TEST_F(PythonGlobalsTests, TryAcceptTwoStrings)
{
PythonGlobalsTester pythonGlobalsTester;
pythonGlobalsTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
stringValue1_has_data,
stringValue2_is_empty
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "stringValue1_has_data"))
{
return aznumeric_cast<int>(LogTypes::stringValue1_has_data);
}
else if (AzFramework::StringFunc::Equal(message, "stringValue2_is_empty"))
{
return aznumeric_cast<int>(LogTypes::stringValue2_is_empty);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
try
{
pybind11::exec(R"(
import azlmbr.globals
azlmbr.globals.accept_two_strings("Test 01", "")
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::stringValue1_has_data)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::stringValue2_is_empty)]);
}
TEST_F(PythonGlobalsTests, GlobalListAllClasses)
{
PythonGlobalsTester pythonGlobalsTester;
pythonGlobalsTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
ClassesFound
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "ClassListFound"))
{
return aznumeric_cast<int>(LogTypes::ClassesFound);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
try
{
pybind11::exec(R"(
import azlmbr.object
classList = azlmbr.object.list_classes()
if (len(classList) > 0):
print ('ClassListFound')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::ClassesFound)]);
}
TEST_F(PythonGlobalsTests, GlobalModuleDefinedTypeId)
{
PythonGlobalsTester pythonGlobalsTester;
pythonGlobalsTester.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
TypeIsValid,
StringTypeIsValid,
EnumIsValid,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "TypeIsValid"))
{
return aznumeric_cast<int>(LogTypes::TypeIsValid);
}
else if (AzFramework::StringFunc::Equal(message, "StringTypeIsValid"))
{
return aznumeric_cast<int>(LogTypes::StringTypeIsValid);
}
else if (AzFramework::StringFunc::Equal(message, "EnumIsValid"))
{
return aznumeric_cast<int>(LogTypes::EnumIsValid);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
try
{
pybind11::exec(R"(
import azlmbr.constant
import azlmbr.my.enum
import azlmbr.globals
azlmbr.globals.reset()
type = azlmbr.constant.MY_TYPE
if (type.ToString().startswith('{DEADBEE5-')):
print ('TypeIsValid')
if (azlmbr.constant.MY_STRING == 'my string'):
print ('StringTypeIsValid')
if (azlmbr.my.enum.One == 1):
print ('EnumIsValid')
if (azlmbr.my.enum.Two == 2):
print ('EnumIsValid')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::TypeIsValid)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::StringTypeIsValid)]);
EXPECT_EQ(2, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::EnumIsValid)]);
}
TEST_F(PythonGlobalsTests, CompareEqualityOperators)
{
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
IsGreaterThan,
IsGreaterEqualTo,
IsLessThan,
IsLessEqualTo,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "IsGreaterThan"))
{
return aznumeric_cast<int>(LogTypes::IsGreaterThan);
}
else if (AzFramework::StringFunc::StartsWith(message, "IsGreaterEqualTo"))
{
return aznumeric_cast<int>(LogTypes::IsGreaterEqualTo);
}
else if (AzFramework::StringFunc::StartsWith(message, "IsLessThan"))
{
return aznumeric_cast<int>(LogTypes::IsLessThan);
}
else if (AzFramework::StringFunc::StartsWith(message, "IsLessEqualTo"))
{
return aznumeric_cast<int>(LogTypes::IsLessEqualTo);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
try
{
pybind11::exec(R"(
import azlmbr.math
import azlmbr.globals
pointA = azlmbr.math.Vector2(40.0)
pointB = azlmbr.math.Vector2(2.0)
if (pointB < pointA):
print ('IsLessThan')
if (pointB <= pointA):
print ('IsLessEqualTo')
if (pointB <= pointB):
print ('IsLessEqualTo')
if (pointA > pointB):
print ('IsGreaterThan')
if (pointA >= pointB):
print ('IsGreaterEqualTo')
if (pointA >= pointA):
print ('IsGreaterEqualTo')
if (pointB >= pointA):
print ('IsGreaterEqualTo')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
Deactivate(e);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::IsGreaterThan)]);
EXPECT_EQ(2, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::IsGreaterEqualTo)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::IsLessThan)]);
EXPECT_EQ(2, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::IsLessEqualTo)]);
}
}
@@ -0,0 +1,214 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
#include <Source/PythonLogSymbolsComponent.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// test classes/structs
class PythonLogSymbolsTestComponent :
public EditorPythonBindings::PythonLogSymbolsComponent
{
public:
AZ_COMPONENT(PythonLogSymbolsTestComponent, "{D5802A34-1B57-470B-8C30-FFC273C9F4ED}", EditorPythonBindings::PythonLogSymbolsComponent);
AZStd::string_view FetchPythonTypeAndTraitsWrapper(const AZ::TypeId& typeId, AZ::u32 traits)
{
return FetchPythonTypeAndTraits(typeId, traits);
}
AZStd::string_view FetchPythonTypeWrapper(const AZ::BehaviorParameter& param)
{
return FetchPythonType(param);
}
};
class SimpleClass
{
public:
AZ_TYPE_INFO(SimpleClass, "{DFA153D8-F168-44F9-8DEF-55CDBBAA5AA2}")
};
class CustomClass
{
public:
AZ_TYPE_INFO(CustomClass, "{361A9A18-40E6-4D16-920A-0F38F55D63BF}")
void NoOp() const
{}
};
struct TestTypesReflectionContainer
{
AZ_TYPE_INFO(TestTypesReflectionContainer, "{5DE28B62-F9A1-4307-9684-6C95B9EE3225}")
void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->RegisterGenericType<AZStd::vector<int>>();
serializeContext->RegisterGenericType<AZStd::vector<SimpleClass>>();
serializeContext->RegisterGenericType<AZStd::vector<CustomClass>>();
serializeContext->RegisterGenericType<AZStd::map<int, int>>();
serializeContext->RegisterGenericType<AZStd::map<int, SimpleClass>>();
serializeContext->RegisterGenericType<AZStd::map<int, CustomClass>>();
serializeContext->RegisterGenericType<AZ::Outcome<int, int>>();
serializeContext->RegisterGenericType<AZ::Outcome<int, SimpleClass>>();
serializeContext->RegisterGenericType<AZ::Outcome<int, CustomClass>>();
serializeContext->Class<CustomClass>()
->Version(1)
;
// SimpleClass registration ommited for testing cases where type cannot be determined.
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonLogSymbolsComponentTest
: public PythonTestingFixture
{
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
// Registering test types
TestTypesReflectionContainer typesContainer;
typesContainer.Reflect(m_app.GetSerializeContext());
typesContainer.Reflect(m_app.GetBehaviorContext());
}
void TearDown() override
{
// clearing up memory
PythonTestingFixture::TearDown();
}
};
//////////////////////////////////////////////////////////////////////////
// tests
TEST_F(PythonLogSymbolsComponentTest, FetchSupportedTypesByTypeAndTraits_PythonTypeReturned)
{
PythonLogSymbolsTestComponent pythonLogSymbolsComponent;
AZStd::vector<AZStd::tuple<AZ::TypeId, AZ::u32, AZStd::string>> typesToTest =
{
// Simple types
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::string_view>::Uuid(), AZ::BehaviorParameter::TR_NONE, "str"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::string>::Uuid(), AZ::BehaviorParameter::TR_NONE, "str"),
AZStd::make_tuple(AZ::AzTypeInfo<char>::Uuid(), AZ::BehaviorParameter::TR_POINTER | AZ::BehaviorParameter::TR_CONST, "str"),
AZStd::make_tuple(AZ::AzTypeInfo<float>::Uuid(), AZ::BehaviorParameter::TR_NONE, "float"),
AZStd::make_tuple(AZ::AzTypeInfo<double>::Uuid(), AZ::BehaviorParameter::TR_NONE, "float"),
AZStd::make_tuple(AZ::AzTypeInfo<bool>::Uuid(), AZ::BehaviorParameter::TR_NONE, "bool"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::s8>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::u8>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::s16>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::u16>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::s32>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::u32>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::s64>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::u64>::Uuid(), AZ::BehaviorParameter::TR_NONE, "int"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::vector<AZ::u8>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "bytes"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::any>::Uuid(), AZ::BehaviorParameter::TR_NONE, "object"),
AZStd::make_tuple(AZ::AzTypeInfo<void>::Uuid(), AZ::BehaviorParameter::TR_NONE, "None"),
// Container types
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::vector<SimpleClass>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "list"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::vector<int>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "List[int]"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::vector<CustomClass>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "List[CustomClass]"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::map<int, SimpleClass>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "dict"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::map<int, int>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "Dict[int, int]"),
AZStd::make_tuple(AZ::AzTypeInfo<AZStd::map<int, CustomClass>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "Dict[int, CustomClass]"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::Outcome<int, SimpleClass>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "Outcome"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::Outcome<int, int>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "Outcome[int, int]"),
AZStd::make_tuple(AZ::AzTypeInfo<AZ::Outcome<int, CustomClass>>::Uuid(), AZ::BehaviorParameter::TR_NONE, "Outcome[int, CustomClass]"),
// Fallback to name
AZStd::make_tuple(AZ::AzTypeInfo<SimpleClass>::Uuid(), AZ::BehaviorParameter::TR_NONE, ""),
AZStd::make_tuple(AZ::AzTypeInfo<CustomClass>::Uuid(), AZ::BehaviorParameter::TR_NONE, "CustomClass")
};
auto stringViewHelper = [](const AZStd::string_view& s)
{
return AZStd::string::format(AZ_STRING_FORMAT, AZ_STRING_ARG(s));
};
auto uuidHelper = [](const AZ::Uuid& uuid)
{
char buffer[AZ::Uuid::MaxStringBuffer];
uuid.ToString(buffer, AZ::Uuid::MaxStringBuffer, true, true);
return AZStd::string(buffer);
};
for (auto& typeInfo : typesToTest)
{
AZStd::string_view result = pythonLogSymbolsComponent.FetchPythonTypeAndTraitsWrapper(AZStd::get<0>(typeInfo), AZStd::get<1>(typeInfo));
EXPECT_EQ(result, AZStd::get<2>(typeInfo))
<< "Expected '" << stringViewHelper(AZStd::get<2>(typeInfo)).c_str()
<< "' when converting type with id " << uuidHelper(AZStd::get<0>(typeInfo)).c_str()
<< " but got '" << stringViewHelper(result).c_str() << "'.";
}
}
TEST_F(PythonLogSymbolsComponentTest, FetchByParam_ReturnPythonType)
{
PythonLogSymbolsTestComponent pythonLogSymbolsComponent;
AZ::BehaviorParameter intParam;
intParam.m_name = "foo";
intParam.m_typeId = AZ::AzTypeInfo<AZ::s8>::Uuid(); // Uuid for a supported type
intParam.m_traits = AZ::BehaviorParameter::TR_NONE;
AZStd::string_view result = pythonLogSymbolsComponent.FetchPythonTypeWrapper(intParam);
EXPECT_EQ(result, "int");
}
TEST_F(PythonLogSymbolsComponentTest, FetchVoidByParam_ReturnNone)
{
PythonLogSymbolsTestComponent m_pythonLogSymbolsComponent;
AZ::BehaviorParameter voidParam;
voidParam.m_name = "void";
voidParam.m_typeId = AZ::Uuid("{9B3E8886-B749-418E-A696-6D7E9EB4D691}"); // A random Uuid
voidParam.m_traits = AZ::BehaviorParameter::TR_NONE;
AZStd::string_view result = m_pythonLogSymbolsComponent.FetchPythonTypeWrapper(voidParam);
EXPECT_EQ(result, "None");
}
}
@@ -0,0 +1,436 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
#include <Source/PythonProxyObject.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include "PythonPairTests.h"
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// test class/structs
struct PythonReflectionPairTypes
{
AZ_TYPE_INFO(PythonReflectionPairTypes, "{037C067F-7A03-47BE-A30E-124D8157EDA2}");
template <typename K, typename V>
struct PairOf
{
using PairType = AZStd::pair<K, V>;
PairType m_pair;
explicit PairOf(const PairType& pair)
{
m_pair = pair;
}
explicit PairOf(const K& k, const V& v)
{
m_pair = PairType(k, v);
}
const PairType& ReturnPair() const
{
return m_pair;
}
void AcceptPair(const PairType& other)
{
m_pair = other;
}
void RegisterGenericType(AZ::SerializeContext& serializeContext)
{
serializeContext.RegisterGenericType<PairType>();
}
};
PairOf<bool, bool> m_pairOfBoolToBool { false, true };
PairOf<AZ::u8, AZ::u32> m_pairOfu8tou32 {1, 4};
PairOf<AZ::u16, float> m_pairOfu16toFloat {1, 0.4f};
PairOf<AZStd::string, AZ::s32> m_pairOfStringTos32 {"1", -4};
PairOf<AZStd::string, AZStd::string> m_pairOfStringToString {"one", "foo"};
PairOf<AZStd::string, MyCustomType> m_pairOfStringToCustomType{ "foo", MyCustomType() };
void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
m_pairOfBoolToBool.RegisterGenericType(*serializeContext);
m_pairOfu8tou32.RegisterGenericType(*serializeContext);
m_pairOfu16toFloat.RegisterGenericType(*serializeContext);
m_pairOfStringTos32.RegisterGenericType(*serializeContext);
m_pairOfStringToString.RegisterGenericType(*serializeContext);
m_pairOfStringToCustomType.RegisterGenericType(*serializeContext);
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<PythonReflectionPairTypes>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test.pair")
->Method("return_pair_of_boolToBool", [](PythonReflectionPairTypes* self) { return self->m_pairOfBoolToBool.ReturnPair(); }, nullptr, "")
->Method("accept_pair_of_boolToBool", [](PythonReflectionPairTypes* self, const PairOf<bool, bool>::PairType& pair) { self->m_pairOfBoolToBool.AcceptPair(pair); }, nullptr, "")
->Method("return_pair_of_u8u32", [](PythonReflectionPairTypes* self) { return self->m_pairOfu8tou32.ReturnPair(); }, nullptr, "")
->Method("accept_pair_of_u8u32", [](PythonReflectionPairTypes* self, const PairOf<AZ::u8, AZ::u32>::PairType& pair) { self->m_pairOfu8tou32.AcceptPair(pair); }, nullptr, "")
->Method("return_pair_of_u16toFloat", [](PythonReflectionPairTypes* self) { return self->m_pairOfu16toFloat.ReturnPair(); }, nullptr, "")
->Method("accept_pair_of_u16toFloat", [](PythonReflectionPairTypes* self, const PairOf<AZ::u16, float>::PairType& pair) { self->m_pairOfu16toFloat.AcceptPair(pair); }, nullptr, "")
->Method("return_pair_of_stringTos32", [](PythonReflectionPairTypes* self) { return self->m_pairOfStringTos32.ReturnPair(); }, nullptr, "")
->Method("accept_pair_of_stringTos32", [](PythonReflectionPairTypes* self, const PairOf<AZStd::string, AZ::s32>::PairType& pair) { self->m_pairOfStringTos32.AcceptPair(pair); }, nullptr, "")
->Method("return_pair_of_stringToString", [](PythonReflectionPairTypes* self) { return self->m_pairOfStringToString.ReturnPair(); }, nullptr, "")
->Method("accept_pair_of_stringToString", [](PythonReflectionPairTypes* self, const PairOf<AZStd::string, AZStd::string>::PairType& pair) { self->m_pairOfStringToString.AcceptPair(pair); }, nullptr, "")
->Method("return_pair_of_stringToCustomType", [](PythonReflectionPairTypes* self) { return self->m_pairOfStringToCustomType.ReturnPair(); }, nullptr, "")
->Method("accept_pair_of_stringToCustomType", [](PythonReflectionPairTypes* self, const PairOf<AZStd::string, MyCustomType>::PairType& pair) { self->m_pairOfStringToCustomType.AcceptPair(pair); }, nullptr, "")
;
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonReflectionPairTests
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
};
TEST_F(PythonReflectionPairTests, SimpleTypes_Constructed)
{
enum class LogTypes
{
Skip = 0,
PairTypeTest_ConstructBoolDefault,
PairTypeTest_ConstructBoolParams,
PairTypeTest_UseConstructed
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "PairTypeTest_ConstructBoolDefault"))
{
return static_cast<int>(LogTypes::PairTypeTest_ConstructBoolDefault);
}
else if (AzFramework::StringFunc::StartsWith(message, "PairTypeTest_ConstructBoolParams"))
{
return static_cast<int>(LogTypes::PairTypeTest_ConstructBoolParams);
}
else if (AzFramework::StringFunc::StartsWith(message, "PairTypeTest_UseConstructed"))
{
return static_cast<int>(LogTypes::PairTypeTest_UseConstructed);
}
}
return static_cast<int>(LogTypes::Skip);
};
MyCustomType::Reflect(m_app.GetSerializeContext());
MyCustomType::Reflect(m_app.GetBehaviorContext());
PythonReflectionPairTypes pythonReflectionPairTypes;
pythonReflectionPairTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionPairTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.pair
import azlmbr.object
import azlmbr.std
test = azlmbr.object.create('PythonReflectionPairTypes')
test_pair = azlmbr.object.create('AZStd::pair<bool, bool>')
if (test_pair):
print ('PairTypeTest_ConstructBoolDefault')
test_pair = azlmbr.object.construct('AZStd::pair<bool, bool>', True, False)
if (test_pair and test_pair.first == True and test_pair.second == False):
print ('PairTypeTest_ConstructBoolParams')
test_pair.first = False
test_pair.second = True
test.accept_pair_of_boolToBool(test_pair)
result = test.return_pair_of_boolToBool()
if (len(result) == 2 and result[0] == False and result[1] == True):
print ('PairTypeTest_UseConstructed')
)");
}
catch ([[maybe_unused]] const std::exception& ex)
{
AZ_Warning("UnitTest", false, "Failed with Python exception of %s", ex.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairTypeTest_ConstructBoolDefault)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairTypeTest_ConstructBoolParams)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairTypeTest_UseConstructed)]);
}
TEST_F(PythonReflectionPairTests, SimpleTypes_ConvertedCorrectly)
{
enum class LogTypes
{
Skip = 0,
PairTypeTest_Input,
PairTypeTest_Output,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "PairTypeTest_Input"))
{
return static_cast<int>(LogTypes::PairTypeTest_Input);
}
else if (AzFramework::StringFunc::StartsWith(message, "PairTypeTest_Output"))
{
return static_cast<int>(LogTypes::PairTypeTest_Output);
}
}
return static_cast<int>(LogTypes::Skip);
};
MyCustomType::Reflect(m_app.GetSerializeContext());
MyCustomType::Reflect(m_app.GetBehaviorContext());
PythonReflectionPairTypes pythonReflectionPairTypes;
pythonReflectionPairTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionPairTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.pair
import azlmbr.object
import azlmbr.std
test = azlmbr.object.create('PythonReflectionPairTypes')
result = test.return_pair_of_u8u32()
if (len(result) == 2):
print ('PairTypeTest_Output_u8u32')
test.accept_pair_of_u8u32([42, 0])
result = test.return_pair_of_u8u32()
if (len(result) == 2 and result[0] == 42 and result[1] == 0):
print ('PairTypeTest_Input_u8u32_list')
test.accept_pair_of_u8u32((1, 2))
result = test.return_pair_of_u8u32()
if (len(result) == 2 and result[0] == 1 and result[1] == 2):
print ('PairTypeTest_Input_u8u32')
result = test.return_pair_of_u16toFloat()
if (len(result) == 2):
print ('PairTypeTest_Output_u16toFloat')
test.accept_pair_of_u16toFloat((4, -0.01))
result = test.return_pair_of_u16toFloat()
if (len(result) == 2 and result[0] == 4 and result[1] < 0):
print ('PairTypeTest_Input_u16toFloat')
result = test.return_pair_of_stringTos32()
if (len(result) == 2):
print ('PairTypeTest_Output_stringTos32')
test.accept_pair_of_stringTos32(('abc', -1))
result = test.return_pair_of_stringTos32()
if (len(result) == 2 and result[0] == 'abc' and result[1] == -1):
print ('PairTypeTest_Input_stringTos32')
result = test.return_pair_of_stringToString()
if (len(result) == 2):
print ('PairTypeTest_Output_stringToString')
test.accept_pair_of_stringToString(('one', 'two'))
result = test.return_pair_of_stringToString()
if (len(result) == 2 and result[0] == 'one' and result[1] == 'two'):
print ('PairTypeTest_Input_stringToString')
)");
}
catch ([[maybe_unused]] const std::exception& ex)
{
AZ_Warning("UnitTest", false, "Failed with Python exception of %s", ex.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(5, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairTypeTest_Input)]);
EXPECT_EQ(4, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairTypeTest_Output)]);
}
TEST_F(PythonReflectionPairTests, CustomTypes_ConvertedCorrectly)
{
enum class LogTypes
{
Skip = 0,
PairCustomTypeTest_Input,
PairCustomTypeTest_Output,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "PairCustomTypeTest_Input"))
{
return static_cast<int>(LogTypes::PairCustomTypeTest_Input);
}
else if (AzFramework::StringFunc::StartsWith(message, "PairCustomTypeTest_Output"))
{
return static_cast<int>(LogTypes::PairCustomTypeTest_Output);
}
}
return static_cast<int>(LogTypes::Skip);
};
MyCustomType::Reflect(m_app.GetSerializeContext());
MyCustomType::Reflect(m_app.GetBehaviorContext());
PythonReflectionPairTypes pythonReflectionPairTypes;
pythonReflectionPairTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionPairTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.pair
import azlmbr.object
import azlmbr.std
test = azlmbr.object.create('PythonReflectionPairTypes')
result = test.return_pair_of_stringToCustomType()
if (len(result) == 2):
print ('PairCustomTypeTest_Output_stringToCustomType')
custom = azlmbr.object.create('MyCustomType')
custom.set_data(42)
test.accept_pair_of_stringToCustomType(('def', custom))
result = test.return_pair_of_stringToCustomType()
if (len(result) == 2):
if (result[0] == 'def' and result[1].get_data() == 42):
print ('PairCustomTypeTest_Input_stringToCustomType_tuple')
)");
}
catch ([[maybe_unused]] const std::exception& ex)
{
AZ_Warning("UnitTest", false, "Failed with Python exception of %s", ex.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairCustomTypeTest_Input)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairCustomTypeTest_Output)]);
}
TEST_F(PythonReflectionPairTests, UnsupportedTypes_ErrorLogged)
{
enum class LogTypes
{
Skip = 0,
PairUnsupportedTypeTest_CannotConvert
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "Cannot convert pair container for"))
{
return static_cast<int>(LogTypes::PairUnsupportedTypeTest_CannotConvert);
}
}
return static_cast<int>(LogTypes::Skip);
};
MyCustomType::Reflect(m_app.GetSerializeContext());
MyCustomType::Reflect(m_app.GetBehaviorContext());
PythonReflectionPairTypes pythonReflectionDictionaryTypes;
pythonReflectionDictionaryTypes.Reflect(m_app.GetSerializeContext());
pythonReflectionDictionaryTypes.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.test.pair
import azlmbr.object
import azlmbr.std
test = azlmbr.object.create('PythonReflectionPairTypes')
test.accept_pair_of_u8u32([42, 0, 1])
test.accept_pair_of_u8u32({42, 0})
)");
}
catch ([[maybe_unused]] const std::exception& ex)
{
AZ_Warning("UnitTest", false, "Failed with Python exception of %s", ex.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(2, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::PairUnsupportedTypeTest_CannotConvert)]);
}
}
@@ -0,0 +1,87 @@
/*
* 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.
*
*/
#pragma once
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
#include <Source/PythonProxyObject.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/std/hash.h>
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// test class/structs
struct MyCustomType
{
AZ_TYPE_INFO(MyCustomType, "{E4BE9816-E3E0-49EA-99B0-D72403461548}");
public:
AZ::u8 m_data;
void SetData(AZ::u8 v)
{
m_data = v;
}
AZ::u8 GetData() const
{
return m_data;
}
static void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<MyCustomType>()
->Version(1)
->Field("data", &MyCustomType::m_data)
;
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<MyCustomType>("MyCustomType")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test.pair")
->Method("set_data", &MyCustomType::SetData)
->Method("get_data", &MyCustomType::GetData)
;
}
}
};
}
// AZStd::hash specialization for UnitTest::MyCustomType, required by BehaviorContext for AZStd::pair with custom types.
template<>
struct AZStd::hash<UnitTest::MyCustomType>
{
typedef UnitTest::MyCustomType argument_type;
typedef AZStd::size_t result_type;
constexpr result_type operator()(const argument_type& value) const
{
return AZStd::hash<AZ::u8>()(value.m_data);
}
};
@@ -0,0 +1,929 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonProxyBus.h>
#include <Source/PythonProxyObject.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// test class/struts
class FakeComponentId
{
public:
AZ_TYPE_INFO(FakeComponentId, "{A0A9A069-9C3D-465A-B7AD-0D6CC803990A}");
AZ_CLASS_ALLOCATOR(FakeComponentId, AZ::SystemAllocator, 0);
FakeComponentId() = default;
bool operator==(const FakeComponentId& rhs) const { return m_id == rhs.m_id; }
bool IsValid() const { return m_id != AZ::InvalidComponentId; }
AZStd::string ToString() const { return AZStd::string::format("[%llu]", m_id); }
void Set(AZ::u64 id)
{
m_id = id;
}
AZ::ComponentId m_id = AZ::InvalidComponentId;
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<FakeComponentId>()
->Version(1)
->Field("ComponentId", &FakeComponentId::m_id)
;
serializeContext->RegisterGenericType<AZStd::vector<FakeComponentId>>();
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<FakeComponentId>("FakeComponentId")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "entity")
->Constructor()
->Method("IsValid", &FakeComponentId::IsValid)
->Method("Equal", &FakeComponentId::operator==)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
->Method("ToString", &FakeComponentId::ToString)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)
->Method("Set", &FakeComponentId::Set)
;
}
}
};
struct PythonTestBroadcastRequests
: AZ::EBusTraits
{
static const bool EnableEventQueue = true;
virtual AZ::u32 GetBits() = 0;
virtual void SetBits(AZ::u32 value) = 0;
virtual void Ping() = 0;
virtual void AcceptProxyList(const AZStd::vector<FakeComponentId>& componentIds) = 0;
};
using PythonTestBroadcastRequestBus = AZ::EBus<PythonTestBroadcastRequests>;
struct PythonTestBroadcastRequestsHandler final
: public PythonTestBroadcastRequestBus::Handler
{
PythonTestBroadcastRequestsHandler()
{
PythonTestBroadcastRequestBus::Handler::BusConnect();
}
virtual ~PythonTestBroadcastRequestsHandler()
{
PythonTestBroadcastRequestBus::Handler::BusDisconnect();
}
AZ::u32 m_bits = 0;
AZ::u32 GetBits() override
{
return m_bits;
}
void SetBits(AZ::u32 value) override
{
m_bits |= value;
}
AZ::u64 m_pingCount = 0;
void Ping() override
{
++m_pingCount;
}
void AcceptProxyList(const AZStd::vector<FakeComponentId>& componentIds) override
{
AZStd::vector<AZ::Component*> components;
for (auto componentId : componentIds)
{
if (componentId.IsValid())
{
AZ_Printf("python", "BasicRequests_AcceptProxyList:%s", componentId.ToString().c_str());
}
else
{
AZ_Warning("python", false, "AcceptProxyList failed - found invalid componentId.");
}
}
}
void Reflect(AZ::ReflectContext* context)
{
FakeComponentId::Reflect(context);
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PythonTestBroadcastRequestBus>("PythonTestBroadcastRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Event("SetBits", &PythonTestBroadcastRequestBus::Events::SetBits)
->Event("GetBits", &PythonTestBroadcastRequestBus::Events::GetBits)
->Event("Ping", &PythonTestBroadcastRequestBus::Events::Ping)
->Event("AcceptProxyList", &PythonTestBroadcastRequestBus::Events::AcceptProxyList)
;
}
}
};
//
struct PythonTestEventRequests
: AZ::EBusTraits
{
static const bool EnableEventQueue = true;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::u32;
virtual AZ::s32 Add(AZ::s32 a, AZ::s32 b) = 0;
virtual void Pong() = 0;
};
using PythonTestEventRequestBus = AZ::EBus<PythonTestEventRequests>;
struct PythonTestEventRequestsHandler final
: public PythonTestEventRequestBus::Handler
{
PythonTestEventRequestsHandler()
{
PythonTestEventRequestBus::Handler::BusConnect(101);
}
virtual ~PythonTestEventRequestsHandler()
{
PythonTestEventRequestBus::Handler::BusDisconnect();
}
AZ::s32 Add(AZ::s32 a, AZ::s32 b) override
{
return a + b;
}
AZ::u64 m_pongCount = 0;
void Pong() override
{
++m_pongCount;
}
void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PythonTestEventRequestBus>("PythonTestEventRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Event("Add", &PythonTestEventRequestBus::Events::Add)
->Event("Pong", &PythonTestEventRequestBus::Events::Pong)
;
}
}
};
// an example of an EBus Notification bus using a single address & BusIdType=NullBusId
struct PythonTestSingleAddressNotifications
: AZ::EBusTraits
{
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~PythonTestSingleAddressNotifications() = default;
virtual void OnPing(AZ::u64 count) = 0;
virtual void OnPong(AZ::u64 count) = 0;
virtual void MultipleInputs(AZ::u64 one, AZ::s8 two, AZStd::string_view three) = 0;
virtual AZStd::string OnAddFish(AZStd::string_view value) = 0;
};
using PythonTestSingleAddressNotificationBus = AZ::EBus<PythonTestSingleAddressNotifications>;
struct PythonTestNotificationHandler final
: public PythonTestSingleAddressNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(PythonTestNotificationHandler, "{97052D15-A4E8-461B-B065-91D16E31C4F7}", AZ::SystemAllocator,
OnPing, OnPong, MultipleInputs, OnAddFish);
virtual ~PythonTestNotificationHandler() = default;
void OnPing(AZ::u64 count) override
{
Call(FN_OnPing, count);
}
void OnPong(AZ::u64 count) override
{
Call(FN_OnPong, count);
}
void MultipleInputs(AZ::u64 one, AZ::s8 two, AZStd::string_view three) override
{
Call(FN_MultipleInputs, one, two, three);
}
AZStd::string OnAddFish(AZStd::string_view value) override
{
AZStd::string result;
CallResult(result, FN_OnAddFish, value);
return result;
}
static AZ::u64 s_pongCount;
static AZ::u64 s_pingCount;
static void DoPing()
{
// notify the listeners about Ping
++s_pingCount;
PythonTestSingleAddressNotificationBus::Broadcast(&PythonTestSingleAddressNotificationBus::Events::OnPing, s_pingCount);
}
static void DoPong()
{
// notify the listeners about Pong
++s_pongCount;
PythonTestSingleAddressNotificationBus::Broadcast(&PythonTestSingleAddressNotificationBus::Events::OnPong, s_pongCount);
}
static AZStd::string DoAddFish(AZStd::string value)
{
AZStd::string result;
PythonTestSingleAddressNotificationBus::BroadcastResult(result, &PythonTestSingleAddressNotificationBus::Events::OnAddFish, value);
return result;
}
static void Reset()
{
s_pingCount = 0;
s_pongCount = 0;
}
void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PythonTestSingleAddressNotificationBus>("PythonTestSingleAddressNotificationBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Handler<PythonTestNotificationHandler>()
->Event("on_ping", &PythonTestSingleAddressNotificationBus::Events::OnPing)
->Event("on_pong", &PythonTestSingleAddressNotificationBus::Events::OnPong)
->Event("MultipleInputs", &PythonTestSingleAddressNotificationBus::Events::MultipleInputs)
->Event("OnAddFish", &PythonTestSingleAddressNotificationBus::Events::OnAddFish)
;
// for testing from Python to send out the events
behaviorContext->Class<PythonTestNotificationHandler>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Method("do_ping", &PythonTestNotificationHandler::DoPing)
->Method("do_pong", &PythonTestNotificationHandler::DoPong)
->Method("do_add_fish", &PythonTestNotificationHandler::DoAddFish)
;
}
}
};
AZ::u64 PythonTestNotificationHandler::s_pongCount = 0;
AZ::u64 PythonTestNotificationHandler::s_pingCount = 0;
// an example of an EBus Notification bus connecting to a bus by id
struct PythonTestByIdNotifications
: public AZ::EBusTraits
{
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
using BusIdType = AZ::s32;
virtual void OnResult(AZ::s64 result) = 0;
};
using PythonTestByIdNotificationBus = AZ::EBus<PythonTestByIdNotifications>;
struct PythonTestByIdNotificationsHandler final
: public PythonTestByIdNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(PythonTestByIdNotificationsHandler, "{5F091D4B-86C4-4D25-B982-2ECAFD8AFF0F}", AZ::SystemAllocator, OnResult);
virtual ~PythonTestByIdNotificationsHandler() = default;
void OnResult(AZ::s64 result) override
{
Call(FN_OnResult, result);
}
void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PythonTestByIdNotificationBus>("PythonTestByIdNotificationBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Handler<PythonTestByIdNotificationsHandler>()
->Event("OnResult", &PythonTestByIdNotificationBus::Events::OnResult)
;
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixture
struct PythonBusProxyTests
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
};
//////////////////////////////////////////////////////////////////////////
// tests
TEST_F(PythonBusProxyTests, ImportEbus)
{
enum class LogTypes
{
Skip = 0,
BasicRequests_ImportEbus,
BasicRequests_ImportEbusCount,
BasicRequests_AcceptProxyList
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "BasicRequests_ImportEbus"))
{
return static_cast<int>(LogTypes::BasicRequests_ImportEbus);
}
else if (AzFramework::StringFunc::Equal(message, "BasicRequests_ImportEbusCount"))
{
return static_cast<int>(LogTypes::BasicRequests_ImportEbusCount);
}
else if (AzFramework::StringFunc::StartsWith(message, "BasicRequests_AcceptProxyList"))
{
return static_cast<int>(LogTypes::BasicRequests_AcceptProxyList);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonTestBroadcastRequestsHandler pythonTestBroadcastRequestsHandler;
pythonTestBroadcastRequestsHandler.Reflect(m_app.GetBehaviorContext());
pythonTestBroadcastRequestsHandler.Reflect(m_app.GetSerializeContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.bus
import azlmbr.entity
import azlmbr.object
eventType = azlmbr.bus.Event
if (eventType != None):
print ('BasicRequests_ImportEbus')
if len(azlmbr.bus.__dict__) > 0:
print ('BasicRequests_ImportEbusCount')
componentId101 = azlmbr.object.create('FakeComponentId')
componentId101.Set(101)
componentId102 = azlmbr.object.create('FakeComponentId')
componentId102.Set(102)
componentList = [componentId101, componentId102]
azlmbr.bus.PythonTestBroadcastRequestBus(azlmbr.bus.Broadcast, 'AcceptProxyList', componentList)
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::BasicRequests_ImportEbus)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::BasicRequests_ImportEbusCount)]);
EXPECT_EQ(2, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::BasicRequests_AcceptProxyList)]);
}
TEST_F(PythonBusProxyTests, BroadcastRequests)
{
enum class LogTypes
{
Skip = 0,
BroadcastRequests_SetBits,
BroadcastRequests_GetBits
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "BroadcastRequests_SetBits"))
{
return static_cast<int>(LogTypes::BroadcastRequests_SetBits);
}
else if (AzFramework::StringFunc::Equal(message, "BroadcastRequests_GetBits"))
{
return static_cast<int>(LogTypes::BroadcastRequests_GetBits);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonTestBroadcastRequestsHandler pythonTestBroadcastRequestsHandler;
pythonTestBroadcastRequestsHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.bus
bits = azlmbr.bus.PythonTestBroadcastRequestBus(azlmbr.bus.Broadcast, 'GetBits')
if (bits == 0):
print ('BroadcastRequests_GetBits')
azlmbr.bus.PythonTestBroadcastRequestBus(azlmbr.bus.Broadcast, 'SetBits', bits | 3)
bits = azlmbr.bus.PythonTestBroadcastRequestBus(azlmbr.bus.Broadcast, 'GetBits')
if (bits == 3):
print ('BroadcastRequests_SetBits')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::BroadcastRequests_SetBits)]);
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::BroadcastRequests_GetBits)]);
}
TEST_F(PythonBusProxyTests, QueueBroadcastRequests)
{
PythonTestBroadcastRequestsHandler pythonTestBroadcastRequestsHandler;
pythonTestBroadcastRequestsHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.bus
for i in range(2019):
azlmbr.bus.PythonTestBroadcastRequestBus(azlmbr.bus.QueueBroadcast, 'Ping')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
EXPECT_EQ(0, pythonTestBroadcastRequestsHandler.m_pingCount);
PythonTestBroadcastRequestBus::ExecuteQueuedEvents();
EXPECT_EQ(2019, pythonTestBroadcastRequestsHandler.m_pingCount);
e.Deactivate();
}
TEST_F(PythonBusProxyTests, EventRequests)
{
enum class LogTypes
{
Skip = 0,
EventRequests_Add
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "EventRequests_Add"))
{
return static_cast<int>(LogTypes::EventRequests_Add);
}
}
return static_cast<int>(LogTypes::Skip);
};
PythonTestEventRequestsHandler pythonTestEventRequestsHandler;
pythonTestEventRequestsHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.bus
import azlmbr.test
address = 101
answer = azlmbr.test.PythonTestEventRequestBus(azlmbr.bus.Event, 'Add', address, 40, 2)
if (answer == 42):
print ('EventRequests_Add')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::EventRequests_Add)]);
}
TEST_F(PythonBusProxyTests, QueueEventRequests)
{
PythonTestEventRequestsHandler pythonTestEventRequestsHandler;
pythonTestEventRequestsHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
pybind11::exec(R"(
import azlmbr.bus
import azlmbr.test
address = 101
for i in range(address * 2):
azlmbr.test.PythonTestEventRequestBus(azlmbr.bus.QueueEvent, 'Pong', address)
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
EXPECT_EQ(0, pythonTestEventRequestsHandler.m_pongCount);
PythonTestEventRequestBus::ExecuteQueuedEvents();
EXPECT_EQ(202, pythonTestEventRequestsHandler.m_pongCount);
e.Deactivate();
}
TEST_F(PythonBusProxyTests, SingleAddressNotifications)
{
PythonTestNotificationHandler pythonTestNotificationHandler;
pythonTestNotificationHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
Notifications_OnPing,
Notifications_OnPong,
Notifications_Match,
Notifications_Multi,
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "Notifications_OnPing"))
{
return static_cast<int>(LogTypes::Notifications_OnPing);
}
else if (AzFramework::StringFunc::Equal(message, "Notifications_OnPong"))
{
return static_cast<int>(LogTypes::Notifications_OnPong);
}
else if (AzFramework::StringFunc::Equal(message, "Notifications_Match"))
{
return static_cast<int>(LogTypes::Notifications_Match);
}
else if (AzFramework::StringFunc::StartsWith(message, "Notifications_Multi"))
{
return static_cast<int>(LogTypes::Notifications_Multi);
}
}
return static_cast<int>(LogTypes::Skip);
};
UnitTest::PythonTestNotificationHandler::Reset();
try
{
pybind11::exec(R"(
import azlmbr.bus
import azlmbr.test
pingCount = 0
pongCount = 0
def OnPing(parameters):
global pingCount
pingCount = parameters[0]
print ('Notifications_OnPing')
def OnPong(parameters):
global pongCount
pongCount = parameters[0]
print ('Notifications_OnPong')
def OnMultipleInputs(parameters):
if(len(parameters) == 3):
print ('Notifications_Multi1')
if(parameters[0] == 1):
print ('Notifications_Multi2')
if(parameters[1] == 2):
print ('Notifications_Multi3')
if(parameters[2] == '3'):
print ('Notifications_Multi4')
handler = azlmbr.bus.NotificationHandler('PythonTestSingleAddressNotificationBus')
handler.connect(None)
handler.add_callback('OnPing', OnPing)
handler.add_callback('OnPong', OnPong)
handler.add_callback('MultipleInputs', OnMultipleInputs)
azlmbr.test.PythonTestSingleAddressNotificationBus(azlmbr.bus.Broadcast, 'MultipleInputs', 1, 2, '3')
for i in range(40):
azlmbr.test.PythonTestNotificationHandler_do_ping()
for i in range(2):
azlmbr.test.PythonTestNotificationHandler_do_pong()
if (pingCount == 40):
print ('Notifications_Match')
if (pongCount == 2):
print ('Notifications_Match')
if ((pingCount + pongCount) == 42):
print ('Notifications_Match')
handler.disconnect()
def OnMultipleInputsAgain(parameters):
if(len(parameters) == 3):
print ('Notifications_Multi5')
if(parameters[0] == 4):
print ('Notifications_Multi6')
if(parameters[1] == 5):
print ('Notifications_Multi7')
if(parameters[2] == 'six'):
print ('Notifications_Multi8')
handler = azlmbr.test.PythonTestSingleAddressNotificationBusHandler()
handler.connect(None)
handler.add_callback('MultipleInputs', OnMultipleInputsAgain)
azlmbr.test.PythonTestSingleAddressNotificationBus(azlmbr.bus.Broadcast, 'MultipleInputs', 4, 5, 'six')
handler.disconnect()
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(40, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::Notifications_OnPing)]);
EXPECT_EQ(2, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::Notifications_OnPong)]);
EXPECT_EQ(3, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::Notifications_Match)]);
EXPECT_EQ(8, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::Notifications_Multi)]);
}
TEST_F(PythonBusProxyTests, NotificationsAtAddress)
{
PythonTestByIdNotificationsHandler pythonTestByIdNotificationsHandler;
pythonTestByIdNotificationsHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
AtAddress_Match
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "AtAddress_Match"))
{
return static_cast<int>(LogTypes::AtAddress_Match);
}
}
return static_cast<int>(LogTypes::Skip);
};
try
{
pybind11::exec(R"(
import azlmbr.bus
import azlmbr.default
answer = 0
def OnResult(parameters):
global answer
answer = int(parameters[0])
handler = azlmbr.bus.NotificationHandler('PythonTestByIdNotificationBus')
handler.connect(101)
handler.add_callback('OnResult', OnResult)
address = 101
result = 40 + 2
azlmbr.bus.PythonTestByIdNotificationBus(azlmbr.bus.Event, 'OnResult', address, result)
if (answer == 42):
print ('AtAddress_Match')
handler.disconnect()
azlmbr.bus.PythonTestByIdNotificationBus(azlmbr.bus.Event, 'OnResult', address, 2)
if (answer == 42):
print ('AtAddress_Match')
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(2, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::AtAddress_Match)]);
}
TEST_F(PythonBusProxyTests, NotificationsWithNoAddress)
{
PythonTestNotificationHandler pythonTestNotificationHandler;
pythonTestNotificationHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
NoAddressConnect
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::Equal(message, "NoAddressConnect"))
{
return static_cast<int>(LogTypes::NoAddressConnect);
}
}
return static_cast<int>(LogTypes::Skip);
};
try
{
pybind11::exec(R"(
import azlmbr.bus
import azlmbr.test
def on_ping(args):
print('NoAddressConnect')
handler = azlmbr.test.PythonTestSingleAddressNotificationBusHandler()
handler.connect()
handler.add_callback('OnPing', on_ping)
azlmbr.test.PythonTestNotificationHandler_do_ping()
handler.disconnect()
azlmbr.test.PythonTestNotificationHandler_do_ping()
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("UnitTest", false, "Failed on with Python exception: %s", e.what());
FAIL();
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::NoAddressConnect)]);
}
TEST_F(PythonBusProxyTests, NotificationsWithResult)
{
PythonTestNotificationHandler pythonTestNotificationHandler;
pythonTestNotificationHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
enum class LogTypes
{
Skip = 0,
WithResult
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "WithResult"))
{
return aznumeric_cast<int>(LogTypes::WithResult);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
try
{
pybind11::exec(R"(
import azlmbr.bus
import azlmbr.test
def on_add_fish(args):
value = args[0] + 'fish'
return value
handler = azlmbr.test.PythonTestSingleAddressNotificationBusHandler()
handler.connect()
handler.add_callback('OnAddFish', on_add_fish)
babblefish = azlmbr.test.PythonTestNotificationHandler_do_add_fish('babble')
if (babblefish == 'babblefish'):
print('WithResult_babblefish')
handler.disconnect()
)");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what());
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::WithResult)]);
}}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,155 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/ComponentApplication.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/CommandLine/CommandRegistrationBus.h>
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <QCoreApplication>
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <Source/PythonSystemComponent.h>
#include <Source/PythonReflectionComponent.h>
#include <Source/PythonMarshalComponent.h>
namespace UnitTest
{
struct CommandRegistrationBusSupression
: public AzFramework::CommandRegistrationBus::Handler
{
CommandRegistrationBusSupression()
{
BusConnect();
}
~CommandRegistrationBusSupression()
{
BusDisconnect();
}
bool RegisterCommand(AZStd::string_view, AZStd::string_view, AZ::u32, AzFramework::CommandFunction) override
{
return true;
}
bool UnregisterCommand(AZStd::string_view) override
{
return true;
}
};
struct PythonTestingFixture
: public ::testing::Test
, protected AzFramework::ApplicationRequests::Bus::Handler
{
class FileIOHelper
{
public:
AZ::IO::LocalFileIO m_fileIO;
AZ::IO::FileIOBase* m_prevFileIO;
FileIOHelper()
{
m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(&m_fileIO);
}
~FileIOHelper()
{
AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
}
};
void SetUp() override
{
// fetch the Engine Root folder
{
int argc = 0;
char** argv = nullptr;
QCoreApplication qtApp(argc, argv);
azsnprintf(m_engineRoot, sizeof(m_engineRoot), AzQtComponents::FindEngineRootDir(nullptr).toLocal8Bit().data());
}
m_fileIOHelper = AZStd::make_unique<FileIOHelper>();
m_fileIOHelper->m_fileIO.SetAlias("@devroot@", m_engineRoot);
m_fileIOHelper->m_fileIO.SetAlias("@engroot@", m_engineRoot);
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Create(appDesc);
AzFramework::ApplicationRequests::Bus::Handler::BusConnect();
}
void TearDown() override
{
AzFramework::ApplicationRequests::Bus::Handler::BusDisconnect();
m_commandRegistrationBusSupression.reset();
m_fileIOHelper.reset();
m_app.Destroy();
}
void SimulateEditorBecomingInitialized(bool useCommandRegistrationBusSupression = true)
{
if (useCommandRegistrationBusSupression)
{
m_commandRegistrationBusSupression = AZStd::make_unique<CommandRegistrationBusSupression>();
}
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
if (editorPythonEventsInterface)
{
editorPythonEventsInterface->StartPython();
}
}
void RegisterComponentDescriptors()
{
m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonSystemComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonReflectionComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonMarshalComponent::CreateDescriptor());
}
void Activate(AZ::Entity& e)
{
e.CreateComponent<EditorPythonBindings::PythonSystemComponent>();
e.CreateComponent<EditorPythonBindings::PythonReflectionComponent>();
e.CreateComponent<EditorPythonBindings::PythonMarshalComponent>();
e.Init();
e.Activate();
}
//////////////////////////////////////////////////////////////////////////
// AzFramework::ApplicationRequests::Bus::Handler
// required pure virtual overrides
void NormalizePath(AZStd::string& ) override {}
void NormalizePathKeepCase(AZStd::string& ) override {}
void CalculateBranchTokenForAppRoot(AZStd::string& ) const override {}
// Gets the engine root path for testing
const char* GetEngineRoot() const override { return m_engineRoot; }
// Retrieves the app root path for testing
const char* GetAppRoot() const override { return m_engineRoot; }
AZ::ComponentApplication m_app;
AZStd::unique_ptr<FileIOHelper> m_fileIOHelper;
AZStd::unique_ptr<CommandRegistrationBusSupression> m_commandRegistrationBusSupression;
char m_engineRoot[1024];
};
}
@@ -0,0 +1,224 @@
/*
* 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.
*
*/
#include <Source/PythonCommon.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include "PythonTraceMessageSink.h"
#include "PythonTestingUtility.h"
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
namespace UnitTest
{
//////////////////////////////////////////////////////////////////////////
// behavior
struct PythonThreadNotifications
: public AZ::EBusTraits
{
virtual AZ::s64 OnNotification(AZ::s64 value) = 0;
};
using PythonThreadNotificationBus = AZ::EBus<PythonThreadNotifications>;
struct PythonThreadNotificationBusHandler final
: public PythonThreadNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(PythonThreadNotificationBusHandler, "{CADEF35D-D88C-4DE0-B5FC-A88D383C124E}", AZ::SystemAllocator,
OnNotification);
virtual ~PythonThreadNotificationBusHandler() = default;
AZ::s64 OnNotification(AZ::s64 value) override
{
AZ::s64 result = 0;
CallResult(result, FN_OnNotification, value);
return result;
}
void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PythonThreadNotificationBus>("PythonThreadNotificationBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "test")
->Handler<PythonThreadNotificationBusHandler>()
->Event("OnNotification", &PythonThreadNotificationBus::Events::OnNotification)
;
}
}
};
//////////////////////////////////////////////////////////////////////////
// fixtures
struct PythonThreadingTest
: public PythonTestingFixture
{
PythonTraceMessageSink m_testSink;
void SetUp() override
{
PythonTestingFixture::SetUp();
PythonTestingFixture::RegisterComponentDescriptors();
}
void TearDown() override
{
// clearing up memory
m_testSink = PythonTraceMessageSink();
PythonTestingFixture::TearDown();
}
};
//////////////////////////////////////////////////////////////////////////
// tests
TEST_F(PythonThreadingTest, PythonInterface_ThreadLogic_Runs)
{
enum class LogTypes
{
Skip = 0,
RanInThread
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "RanInThread"))
{
return aznumeric_cast<int>(LogTypes::RanInThread);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
PythonThreadNotificationBusHandler pythonThreadNotificationBusHandler;
pythonThreadNotificationBusHandler.Reflect(m_app.GetSerializeContext());
pythonThreadNotificationBusHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
// prepare handler on this thread
pybind11::exec(R"(
import azlmbr.test
def on_notification(args):
value = args[0] + 2
print ('RanInThread')
return value
handler = azlmbr.test.PythonThreadNotificationBusHandler()
handler.connect()
handler.add_callback('OnNotification', on_notification)
)");
// start thread; in thread issue notification
auto threadCallback = []()
{
AZ::s64 result = 0;
auto notificationCallback = [&result]()
{
PythonThreadNotificationBus::BroadcastResult(result, &PythonThreadNotificationBus::Events::OnNotification, 40);
};
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
if (editorPythonEventsInterface)
{
editorPythonEventsInterface->ExecuteWithLock(notificationCallback);
}
EXPECT_EQ(42, result);
};
AZStd::thread theThread(threadCallback);
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
theThread.join();
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed during thread test with %s", e.what());
}
e.Deactivate();
EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::RanInThread)]);
}
TEST_F(PythonThreadingTest, PythonInterface_ThreadLogic_HandlesPythonException)
{
PythonThreadNotificationBusHandler pythonThreadNotificationBusHandler;
pythonThreadNotificationBusHandler.Reflect(m_app.GetSerializeContext());
pythonThreadNotificationBusHandler.Reflect(m_app.GetBehaviorContext());
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
AZ_TEST_START_TRACE_SUPPRESSION;
// prepare handler on this thread, but will throw a Python exception
pybind11::exec(R"(
import azlmbr.test
def on_notification(args):
raise NotImplementedError("boom")
handler = azlmbr.test.PythonThreadNotificationBusHandler()
handler.connect()
handler.add_callback('OnNotification', on_notification)
)");
// start thread; in thread issue notification
auto threadCallback = []()
{
AZ::s64 result = 0;
auto notificationCallback = [&result]()
{
PythonThreadNotificationBus::BroadcastResult(result, &PythonThreadNotificationBus::Events::OnNotification, 40);
};
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
if (editorPythonEventsInterface)
{
editorPythonEventsInterface->ExecuteWithLock(notificationCallback);
}
EXPECT_EQ(0, result);
};
AZStd::thread theThread(threadCallback);
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
theThread.join();
// the Python script above raises an exception which causes two AZ_Error() message lines:
// "Python callback threw an exception NotImplementedError : boom At : <string>(6) : on_notification"
// "Python callback threw an exception TypeError : 'NoneType' object is not callable At : <string>(7) : on_notification"
AZ_TEST_STOP_TRACE_SUPPRESSION(2);
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed during thread test with %s", e.what());
}
e.Deactivate();
}
}
@@ -0,0 +1,89 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Debug/TraceMessagesDrillerBus.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
namespace UnitTest
{
/** Trace message handler to track messages during tests
*/
struct PythonTraceMessageSink final
: public AZ::Debug::TraceMessageDrillerBus::Handler
, public AzToolsFramework::EditorPythonConsoleNotificationBus::Handler
{
PythonTraceMessageSink()
{
AZ::Debug::TraceMessageDrillerBus::Handler::BusConnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect();
}
~PythonTraceMessageSink()
{
AZ::Debug::TraceMessageDrillerBus::Handler::BusDisconnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
}
// returns an index-tag for a message type that will be counted inside of m_evaluationMap
using EvaluateMessageFunc = AZStd::function<int(const char* window, const char* message)>;
EvaluateMessageFunc m_evaluateMessage;
using EvaluationMap = AZStd::unordered_map<int, int>; // tag to count
EvaluationMap m_evaluationMap;
//////////////////////////////////////////////////////////////////////////
// TraceMessageDrillerBus
void OnPrintf(const char* window, const char* message) override
{
OnOutput(window, message);
}
void OnOutput(const char* window, const char* message) override
{
if (m_evaluateMessage)
{
int key = m_evaluateMessage(window, message);
if (key != 0)
{
auto entryIt = m_evaluationMap.find(key);
if (m_evaluationMap.end() == entryIt)
{
m_evaluationMap[key] = 1;
}
else
{
m_evaluationMap[key] = m_evaluationMap[key] + 1;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorPythonConsoleNotificationBus
void OnTraceMessage([[maybe_unused]] AZStd::string_view message) override
{
AZ_TracePrintf("python", "%.*s", static_cast<int>(message.size()), message.data());
}
void OnErrorMessage([[maybe_unused]] AZStd::string_view message) override
{
AZ_Error("python", false, "%.*s", static_cast<int>(message.size()), message.data());
}
void OnExceptionMessage([[maybe_unused]] AZStd::string_view message) override
{
AZ_Error("python", false, "EXCEPTION: %.*s", static_cast<int>(message.size()), message.data());
}
};
}
@@ -0,0 +1,12 @@
"""
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.
"""
@@ -0,0 +1,12 @@
"""
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.
"""
@@ -0,0 +1,22 @@
"""
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.
"""
#
# does some work
#
import sys
import os
import azlmbr
import azlmbrtest
def print_entity_id(entityId):
print ('entity_id {} {}'.format(entityId.id, entityId.isValid()))
@@ -0,0 +1,21 @@
"""
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.
"""
#
# testing mulitple test modules with azlmbr
#
import azlmbr
import azlmbrtest
import do_work
def test_many_entity_id():
do_work.print_entity_id(azlmbrtest.EntityId(101))
@@ -0,0 +1,25 @@
"""
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.
"""
#
# testing Python import package
#
import sys
def test_call():
print ('test_call_hit')
class TestType:
def __init__(self, *args, **kwargs):
return super().__init__(*args, **kwargs)
def do_call(self, value):
print ('TestType.do_call.{}'.format(value))