/* * 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 (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 (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 (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 (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 (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 (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 (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 (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 (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)]); }}