PythonDictionaryTests.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "PythonTestingUtility.h"
  9. #include "PythonTraceMessageSink.h"
  10. #include <EditorPythonBindings/PythonCommon.h>
  11. #include <pybind11/embed.h>
  12. #include <pybind11/pybind11.h>
  13. #include <Source/PythonSystemComponent.h>
  14. #include <Source/PythonReflectionComponent.h>
  15. #include <Source/PythonMarshalComponent.h>
  16. #include <AzCore/Math/Vector3.h>
  17. #include <AzCore/RTTI/BehaviorContext.h>
  18. #include <AzFramework/StringFunc/StringFunc.h>
  19. namespace UnitTest
  20. {
  21. struct PythonReflectionDictionaryTypes final
  22. {
  23. AZ_TYPE_INFO(PythonReflectionDictionaryTypes, "{478AD363-467D-4285-BE40-4D1CB1A09A19}");
  24. template <typename K, typename V>
  25. struct MapOf
  26. {
  27. using MapType = AZStd::unordered_map<K,V>;
  28. MapType m_map;
  29. explicit MapOf(const std::initializer_list<AZStd::pair<K, V>> map)
  30. {
  31. m_map = map;
  32. }
  33. const MapType& ReturnMap() const
  34. {
  35. return m_map;
  36. }
  37. void AcceptMap(const MapType& other)
  38. {
  39. m_map = other;
  40. }
  41. void RegisterGenericType(AZ::SerializeContext& serializeContext)
  42. {
  43. serializeContext.RegisterGenericType<AZStd::unordered_map<K,V>>();
  44. }
  45. };
  46. MapOf<AZ::u8, AZ::u32> m_indexOfu8tou32 { {AZ::u8(1), 4u}, {AZ::u8(2), 5u}, {AZ::u8(3), 6u}, {AZ::u8(4), 7u} };
  47. MapOf<AZ::u16, float> m_indexOfu16toFloat { {AZ::u16(1u), 0.4f}, {AZ::u16(2u), 0.5f}, {AZ::u16(3u), 0.6f}, {AZ::u16(4u), 0.7f} };
  48. MapOf<AZStd::string, AZ::s32> m_indexOfStringTos32 { {"1", -4}, {"2", 5}, {"3", -6}, {"4", 7} };
  49. MapOf<AZStd::string, AZStd::string> m_indexOfStringToString { {"hello", "foo"}, {"world", "bar"}, {"bye", "baz"}, {"sky", "qux"} };
  50. MapOf<AZStd::string, AZ::Vector3> m_indexOfStringToVec3{ {"up", AZ::Vector3{ 0, 1.0, 0 }}, {"down", AZ::Vector3{0, -1.0, 0}},
  51. {"left", AZ::Vector3{1.0, 0, 0}}, {"right", AZ::Vector3{-1, 0, 0}} };
  52. void Reflect(AZ::ReflectContext* context)
  53. {
  54. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  55. {
  56. m_indexOfu8tou32.RegisterGenericType(*serializeContext);
  57. m_indexOfu16toFloat.RegisterGenericType(*serializeContext);
  58. m_indexOfStringTos32.RegisterGenericType(*serializeContext);
  59. m_indexOfStringToString.RegisterGenericType(*serializeContext);
  60. m_indexOfStringToVec3.RegisterGenericType(*serializeContext);
  61. }
  62. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  63. {
  64. behaviorContext->Class<PythonReflectionDictionaryTypes>()
  65. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  66. ->Attribute(AZ::Script::Attributes::Module, "test.dictionary")
  67. ->Method("return_dict_of_u8u32", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfu8tou32.ReturnMap(); }, nullptr, "")
  68. ->Method("accept_dict_of_u8u32", [](PythonReflectionDictionaryTypes* self, const MapOf<AZ::u8, AZ::u32>::MapType& map) { self->m_indexOfu8tou32.AcceptMap(map); }, nullptr, "")
  69. ->Method("return_dict_of_u16toFloat", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfu16toFloat.ReturnMap(); }, nullptr, "")
  70. ->Method("accept_dict_of_u16toFloat", [](PythonReflectionDictionaryTypes* self, const MapOf<AZ::u16, float>::MapType& map) { self->m_indexOfu16toFloat.AcceptMap(map); }, nullptr, "")
  71. ->Method("return_dict_of_stringTos32", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfStringTos32.ReturnMap(); }, nullptr, "")
  72. ->Method("accept_dict_of_stringTos32", [](PythonReflectionDictionaryTypes* self, const MapOf<AZStd::string, AZ::s32>::MapType& map) { self->m_indexOfStringTos32.AcceptMap(map); }, nullptr, "")
  73. ->Method("return_dict_of_stringToString", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfStringToString.ReturnMap(); }, nullptr, "")
  74. ->Method("accept_dict_of_stringToString", [](PythonReflectionDictionaryTypes* self, const MapOf<AZStd::string, AZStd::string>::MapType& map) { self->m_indexOfStringToString.AcceptMap(map); }, nullptr, "")
  75. ->Method("return_dict_of_stringToVec3", [](PythonReflectionDictionaryTypes* self) { return self->m_indexOfStringToVec3.ReturnMap(); }, nullptr, "")
  76. ->Method("accept_dict_of_stringToVec3", [](PythonReflectionDictionaryTypes* self, const MapOf<AZStd::string, AZ::Vector3>::MapType& map) { self->m_indexOfStringToVec3.AcceptMap(map); }, nullptr, "")
  77. ;
  78. }
  79. }
  80. };
  81. //////////////////////////////////////////////////////////////////////////
  82. // fixtures
  83. struct PythonReflectionDictionaryTests
  84. : public PythonTestingFixture
  85. {
  86. PythonTraceMessageSink m_testSink;
  87. void SetUp() override
  88. {
  89. PythonTestingFixture::SetUp();
  90. PythonTestingFixture::RegisterComponentDescriptors();
  91. }
  92. void TearDown() override
  93. {
  94. // clearing up memory
  95. m_testSink.CleanUp();
  96. PythonTestingFixture::TearDown();
  97. }
  98. };
  99. TEST_F(PythonReflectionDictionaryTests, InstallingPythonDictionaries)
  100. {
  101. AZ::Entity e;
  102. Activate(e);
  103. EXPECT_EQ(AZ::Entity::State::Active, e.GetState());
  104. SimulateEditorBecomingInitialized();
  105. e.Deactivate();
  106. }
  107. TEST_F(PythonReflectionDictionaryTests, MapSimpleTypes)
  108. {
  109. enum class LogTypes
  110. {
  111. Skip = 0,
  112. ContainerTypes_Input,
  113. ContainerTypes_Output,
  114. };
  115. m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
  116. {
  117. if (AzFramework::StringFunc::Equal(window, "python"))
  118. {
  119. if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Input"))
  120. {
  121. return static_cast<int>(LogTypes::ContainerTypes_Input);
  122. }
  123. else if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Output"))
  124. {
  125. return static_cast<int>(LogTypes::ContainerTypes_Output);
  126. }
  127. }
  128. return static_cast<int>(LogTypes::Skip);
  129. };
  130. PythonReflectionDictionaryTypes pythonReflectionDictionaryTypes;
  131. pythonReflectionDictionaryTypes.Reflect(m_app.GetSerializeContext());
  132. pythonReflectionDictionaryTypes.Reflect(m_app.GetBehaviorContext());
  133. AZ::Entity e;
  134. Activate(e);
  135. SimulateEditorBecomingInitialized();
  136. try
  137. {
  138. pybind11::exec(R"(
  139. import azlmbr.test.dictionary
  140. import azlmbr.object
  141. test = azlmbr.object.create('PythonReflectionDictionaryTypes')
  142. result = test.return_dict_of_u8u32()
  143. if (len(result.items()) == 4):
  144. print ('ContainerTypes_Output_u8u32')
  145. test.accept_dict_of_u8u32({4: 1, 3: 2})
  146. result = test.return_dict_of_u8u32()
  147. if (len(result.items()) == 2):
  148. print ('ContainerTypes_Input_u8u32')
  149. result = test.return_dict_of_u16toFloat()
  150. if (len(result.items()) == 4):
  151. print ('ContainerTypes_Output_u16toFloat')
  152. test.accept_dict_of_u16toFloat({4: 0.1, 3: 0.2})
  153. result = test.return_dict_of_u16toFloat()
  154. if (len(result.items()) == 2):
  155. print ('ContainerTypes_Input_u16toFloat')
  156. result = test.return_dict_of_stringTos32()
  157. if (len(result.items()) == 4):
  158. print ('ContainerTypes_Output_stringTos32')
  159. test.accept_dict_of_stringTos32({'4': -1, '3': 2})
  160. result = test.return_dict_of_stringTos32()
  161. if (len(result.items()) == 2):
  162. print ('ContainerTypes_Input_stringTos32')
  163. result = test.return_dict_of_stringToString()
  164. if (len(result.items()) == 4):
  165. print ('ContainerTypes_Output_stringToString')
  166. test.accept_dict_of_stringToString({'one': '1', 'two': '2'})
  167. result = test.return_dict_of_stringToString()
  168. if (len(result.items()) == 2):
  169. print ('ContainerTypes_Input_stringToString')
  170. )");
  171. }
  172. catch ([[maybe_unused]] const std::exception& e)
  173. {
  174. AZ_Warning("UnitTest", false, "Failed with Python exception of %s", e.what());
  175. FAIL();
  176. }
  177. e.Deactivate();
  178. EXPECT_EQ(4, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Input)]);
  179. EXPECT_EQ(4, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Output)]);
  180. }
  181. TEST_F(PythonReflectionDictionaryTests, MapTypes_Mismatch_Detected)
  182. {
  183. enum class LogTypes
  184. {
  185. Skip = 0,
  186. Detection,
  187. };
  188. m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
  189. {
  190. constexpr AZStd::string_view warningTypeMismatch =
  191. "Could not convert to pair element type value2 for the pair<>; failed to marshal Python input <class 'int'>";
  192. constexpr AZStd::string_view warningSizeMismatch =
  193. "Python Dict size:2 does not match the size of the unordered_map:0";
  194. if (AzFramework::StringFunc::Equal(window, "python"))
  195. {
  196. if (AzFramework::StringFunc::StartsWith(message, warningTypeMismatch))
  197. {
  198. return aznumeric_cast<int>(LogTypes::Detection);
  199. }
  200. else if (AzFramework::StringFunc::StartsWith(message, warningSizeMismatch))
  201. {
  202. return aznumeric_cast<int>(LogTypes::Detection);
  203. }
  204. }
  205. return aznumeric_cast<int>(LogTypes::Skip);
  206. };
  207. PythonReflectionDictionaryTypes pythonReflectionDictionaryTypes;
  208. pythonReflectionDictionaryTypes.Reflect(m_app.GetSerializeContext());
  209. pythonReflectionDictionaryTypes.Reflect(m_app.GetBehaviorContext());
  210. AZ::Entity e;
  211. Activate(e);
  212. SimulateEditorBecomingInitialized();
  213. try
  214. {
  215. pybind11::exec(R"(
  216. import azlmbr.test.dictionary
  217. import azlmbr.object
  218. test = azlmbr.object.create('PythonReflectionDictionaryTypes')
  219. mismatchMap = {'one': 1, 'two': 2}
  220. test.accept_dict_of_stringToString(mismatchMap)
  221. )");
  222. }
  223. catch ([[maybe_unused]] const std::exception& e)
  224. {
  225. AZ_Error("UnitTest", false, "Failed with Python exception of %s", e.what());
  226. }
  227. e.Deactivate();
  228. EXPECT_EQ(3, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Detection)]);
  229. }
  230. TEST_F(PythonReflectionDictionaryTests, MapComplexTypes)
  231. {
  232. enum class LogTypes
  233. {
  234. Skip = 0,
  235. ContainerTypes_Input,
  236. ContainerTypes_Output,
  237. };
  238. m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
  239. {
  240. if (AzFramework::StringFunc::Equal(window, "python"))
  241. {
  242. if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Input"))
  243. {
  244. return static_cast<int>(LogTypes::ContainerTypes_Input);
  245. }
  246. else if (AzFramework::StringFunc::StartsWith(message, "ContainerTypes_Output"))
  247. {
  248. return static_cast<int>(LogTypes::ContainerTypes_Output);
  249. }
  250. }
  251. return static_cast<int>(LogTypes::Skip);
  252. };
  253. PythonReflectionDictionaryTypes pythonReflectionDictionaryTypes;
  254. pythonReflectionDictionaryTypes.Reflect(m_app.GetSerializeContext());
  255. pythonReflectionDictionaryTypes.Reflect(m_app.GetBehaviorContext());
  256. AZ::Entity e;
  257. Activate(e);
  258. SimulateEditorBecomingInitialized();
  259. try
  260. {
  261. pybind11::exec(R"(
  262. import azlmbr.test.dictionary
  263. import azlmbr.object
  264. test = azlmbr.object.create('PythonReflectionDictionaryTypes')
  265. result = test.return_dict_of_stringToVec3()
  266. if (len(result.items()) == 4):
  267. print ('ContainerTypes_Output_stringToVec3')
  268. vec3dict = {}
  269. vec3dict['120'] = azlmbr.math.Vector3(1.0, -2.0, 0.0)
  270. vec3dict['456'] = azlmbr.math.Vector3(0.4, 0.5, 0.6)
  271. test.accept_dict_of_stringToVec3(vec3dict)
  272. result = test.return_dict_of_stringToVec3()
  273. if (len(result.items()) == 2):
  274. if (result['120'].x > 0 and result['120'].y < 0 and result['120'].z == 0):
  275. print ('ContainerTypes_Input_stringToVec3')
  276. )");
  277. }
  278. catch ([[maybe_unused]] const std::exception& e)
  279. {
  280. AZ_Warning("UnitTest", false, "Failed with Python exception of %s", e.what());
  281. FAIL();
  282. }
  283. e.Deactivate();
  284. EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Input)]);
  285. EXPECT_EQ(1, m_testSink.m_evaluationMap[static_cast<int>(LogTypes::ContainerTypes_Output)]);
  286. }
  287. }