Integrating latest 47acbe8
This commit is contained in:
@@ -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))
|
||||
Reference in New Issue
Block a user