PythonContainerAnyTests.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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 <AzCore/PlatformDef.h>
  9. #include <EditorPythonBindings/PythonCommon.h>
  10. #include <pybind11/embed.h>
  11. #include <pybind11/pybind11.h>
  12. #include "PythonTraceMessageSink.h"
  13. #include "PythonTestingUtility.h"
  14. #include <Source/PythonSystemComponent.h>
  15. #include <Source/PythonReflectionComponent.h>
  16. #include <Source/PythonMarshalComponent.h>
  17. #include <Source/PythonProxyObject.h>
  18. #include <AzCore/RTTI/BehaviorContext.h>
  19. #include <AzFramework/StringFunc/StringFunc.h>
  20. namespace CustomTest
  21. {
  22. template <typename T>
  23. struct MyTemplate
  24. {
  25. MyTemplate() = default;
  26. MyTemplate(T value) : m_value(value) {}
  27. T m_value = {};
  28. };
  29. }
  30. namespace AZ
  31. {
  32. template<typename T>
  33. struct OnDemandReflection<CustomTest::MyTemplate<T>>
  34. {
  35. using MyTemplateType = CustomTest::MyTemplate<T>;
  36. static void Reflect(ReflectContext* context)
  37. {
  38. if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
  39. {
  40. behaviorContext->Class<MyTemplateType>()
  41. ->Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Automation)
  42. ->Attribute(Script::Attributes::Module, "test.template")
  43. ->Property("Value",
  44. [] (MyTemplateType* that) -> T { return that->m_value; },
  45. [] (MyTemplateType* that, const T& value) { that->m_value = value; })
  46. ;
  47. }
  48. }
  49. };
  50. AZ_TYPE_INFO_TEMPLATE(CustomTest::MyTemplate, "{82B9D060-F077-4FAA-9EF4-EF4C3A2A6332}", AZ_TYPE_INFO_CLASS);
  51. }
  52. namespace UnitTest
  53. {
  54. //////////////////////////////////////////////////////////////////////////
  55. // test class/struts
  56. struct CustomTypeHolder
  57. {
  58. AZ_TYPE_INFO(CustomTypeHolder, "{46543B40-D8AF-4498-BCD0-2FF2A040B42C}");
  59. CustomTest::MyTemplate<float> m_testFloat;
  60. CustomTest::MyTemplate<AZStd::string> m_testString;
  61. CustomTest::MyTemplate<int> m_testInt;
  62. CustomTypeHolder()
  63. : m_testFloat(42.0f)
  64. , m_testString("42")
  65. , m_testInt(42)
  66. {
  67. }
  68. void Reflect(AZ::ReflectContext* context)
  69. {
  70. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  71. {
  72. serializeContext->RegisterGenericType<CustomTest::MyTemplate<float>>();
  73. serializeContext->RegisterGenericType<CustomTest::MyTemplate<AZStd::string>>();
  74. serializeContext->RegisterGenericType<CustomTest::MyTemplate<int>>();
  75. }
  76. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  77. {
  78. behaviorContext->Class<CustomTypeHolder>()
  79. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  80. ->Attribute(AZ::Script::Attributes::Module, "test")
  81. ->Method("set_float", [](CustomTypeHolder& self, float value) { self.m_testFloat.m_value = value; })
  82. ->Property("test_float", BehaviorValueProperty(&CustomTypeHolder::m_testFloat))
  83. ->Property("test_string", BehaviorValueProperty(&CustomTypeHolder::m_testString))
  84. ->Property("test_int", BehaviorValueProperty(&CustomTypeHolder::m_testInt))
  85. ;
  86. }
  87. }
  88. };
  89. struct Descriptor final
  90. {
  91. AZ_TYPE_INFO(Descriptor, "{0DFEE628-EFE2-4B9B-BAF2-40ED2965E663}");
  92. void Reflect(AZ::ReflectContext* context)
  93. {
  94. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  95. {
  96. serializeContext->RegisterGenericType<Descriptor>();
  97. serializeContext->RegisterGenericType<AZStd::vector<Descriptor>>();
  98. }
  99. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  100. {
  101. behaviorContext->Class<Descriptor>()
  102. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  103. ->Attribute(AZ::Script::Attributes::Module, "test")
  104. ->Property("s32", BehaviorValueProperty(&Descriptor::m_s32))
  105. ->Property("u32", BehaviorValueProperty(&Descriptor::m_u32))
  106. ->Property("scalar", BehaviorValueProperty(&Descriptor::m_scalar))
  107. ->Property("bool_value", BehaviorValueProperty(&Descriptor::m_bool))
  108. ->Property("string_value", BehaviorValueProperty(&Descriptor::m_stringValue))
  109. ->Method("return_dummy_descriptor", []() { static Descriptor dummy; return dummy; }, nullptr, "")
  110. ->Method("return_dummy_vector_descriptor", []() { static AZStd::vector<Descriptor> dummy; return dummy; }, nullptr, "")
  111. ;
  112. }
  113. }
  114. Descriptor() = default;
  115. ~Descriptor() = default;
  116. AZ::s32 m_s32 = -1234;
  117. AZ::u32 m_u32 = 0xDEADBEEF;
  118. float m_scalar = -456.0f;
  119. bool m_bool = true;
  120. AZStd::string m_stringValue;
  121. };
  122. struct PythonReflectionAnyContainer
  123. {
  124. AZ_TYPE_INFO(PythonReflectionAnyContainer, "{D7D45479-9A46-469E-BE75-F305EBE8F848}");
  125. AZStd::any m_anyList; // will store a container like vector
  126. PythonReflectionAnyContainer()
  127. {
  128. AZStd::vector<AZ::s64> numbers{ 1,2,3,5,8,13 };
  129. m_anyList = AZStd::make_any<AZStd::vector<AZ::s64>>(numbers);
  130. }
  131. void MutateAnyContainer(const AZStd::any& value)
  132. {
  133. m_anyList = value;
  134. if(m_anyList.is<AZStd::vector<Descriptor>>())
  135. {
  136. const AZStd::vector<Descriptor>* ptr = AZStd::any_cast<AZStd::vector<Descriptor>>(&m_anyList);
  137. if (!ptr->empty())
  138. {
  139. AZ_Printf("python", "ReplaceAnyList_AZStd::vector<Descriptor>", ptr->size());
  140. }
  141. }
  142. }
  143. const AZStd::any& AccessAnyContainer() const
  144. {
  145. if (m_anyList.is<AZStd::vector<Descriptor>>())
  146. {
  147. const AZStd::vector<Descriptor>* ptr = AZStd::any_cast<AZStd::vector<Descriptor>>(&m_anyList);
  148. if (!ptr->empty())
  149. {
  150. AZ_Printf("python", "AccessAnyList_AZStd::vector<Descriptor>", ptr->size());
  151. }
  152. }
  153. return m_anyList;
  154. }
  155. void Reflect(AZ::ReflectContext* context)
  156. {
  157. using namespace EditorPythonBindings;
  158. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  159. {
  160. serializeContext->RegisterGenericType<AZStd::vector<AZStd::any>>();
  161. serializeContext->RegisterGenericType<AZStd::vector<AZ::s64>>();
  162. serializeContext->RegisterGenericType<AZStd::vector<double>>();
  163. serializeContext->RegisterGenericType<AZStd::vector<bool>>();
  164. serializeContext->RegisterGenericType<AZStd::vector<AZStd::string>>();
  165. serializeContext->RegisterGenericType<AZStd::vector<PythonProxyObject>>();
  166. ;
  167. }
  168. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  169. {
  170. behaviorContext->Class<PythonReflectionAnyContainer>()
  171. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  172. ->Attribute(AZ::Script::Attributes::Module, "test")
  173. ->Method("mutate_any_container", &PythonReflectionAnyContainer::MutateAnyContainer, nullptr, "")
  174. ->Method("access_any_container", &PythonReflectionAnyContainer::AccessAnyContainer, nullptr, "")
  175. ->Method("return_dummy_vector_integer", []() { static AZStd::vector<AZ::s64> dummy; return dummy; }, nullptr, "")
  176. ->Method("return_dummy_vector_double", []() { static AZStd::vector<double> dummy; return dummy; }, nullptr, "")
  177. ->Method("return_dummy_vector_bool", []() { static AZStd::vector<bool> dummy; return dummy; }, nullptr, "")
  178. ->Method("return_dummy_vector_string", []() { static AZStd::vector<AZStd::string> dummy; return dummy; }, nullptr, "")
  179. ->Method("return_dummy_vector_proxy", []() { static AZStd::vector<PythonProxyObject> dummy; return dummy; }, nullptr, "")
  180. ;
  181. }
  182. }
  183. };
  184. //////////////////////////////////////////////////////////////////////////
  185. // fixtures
  186. struct PythonReflectAnyContainerTests
  187. : public PythonTestingFixture
  188. {
  189. PythonTraceMessageSink m_testSink;
  190. void SetUp() override
  191. {
  192. PythonTestingFixture::SetUp();
  193. PythonTestingFixture::RegisterComponentDescriptors();
  194. }
  195. void TearDown() override
  196. {
  197. // clearing up memory
  198. m_testSink.CleanUp();
  199. PythonTestingFixture::TearDown();
  200. }
  201. };
  202. TEST_F(PythonReflectAnyContainerTests, AccessReplaceVectorTypes)
  203. {
  204. enum class LogTypes
  205. {
  206. Skip = 0,
  207. AccessAnyList,
  208. ReplaceAnyList,
  209. };
  210. m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
  211. {
  212. if (AzFramework::StringFunc::Equal(window, "python"))
  213. {
  214. if (AzFramework::StringFunc::StartsWith(message, "AccessAnyList"))
  215. {
  216. return aznumeric_cast<int>(LogTypes::AccessAnyList);
  217. }
  218. else if (AzFramework::StringFunc::StartsWith(message, "ReplaceAnyList"))
  219. {
  220. return aznumeric_cast<int>(LogTypes::ReplaceAnyList);
  221. }
  222. }
  223. return aznumeric_cast<int>(LogTypes::Skip);
  224. };
  225. PythonReflectionAnyContainer pythonReflectionAnyContainer;
  226. pythonReflectionAnyContainer.Reflect(m_app.GetBehaviorContext());
  227. pythonReflectionAnyContainer.Reflect(m_app.GetSerializeContext());
  228. AZ::Entity e;
  229. Activate(e);
  230. SimulateEditorBecomingInitialized();
  231. try
  232. {
  233. pybind11::exec(R"(
  234. import azlmbr.test as test
  235. testObject = test.PythonReflectionAnyContainer()
  236. target = [1,2,3,5,8,13]
  237. values = testObject.access_any_container()
  238. if (len(values) > 0):
  239. print ('AccessAnyList_for_values')
  240. if (values == target):
  241. print ('AccessAnyList_matching_ends')
  242. target.reverse()
  243. testObject.mutate_any_container(target)
  244. values = testObject.access_any_container()
  245. if (values == target):
  246. print ('ReplaceAnyList_replaced_as_reversed')
  247. target = [True,False,True,True]
  248. testObject.mutate_any_container(target)
  249. values = testObject.access_any_container()
  250. if( type(values[0]) is bool):
  251. print ('AccessAnyList_matching_bools')
  252. target.reverse()
  253. testObject.mutate_any_container(target)
  254. values = testObject.access_any_container()
  255. if (values == target):
  256. print ('ReplaceAnyList_replaced_bools')
  257. target = [-1.0,1.0,-10.0,10.0]
  258. testObject.mutate_any_container(target)
  259. values = testObject.access_any_container()
  260. if (values == target):
  261. print ('AccessAnyList_matching_floats')
  262. target.reverse()
  263. testObject.mutate_any_container(target)
  264. values = testObject.access_any_container()
  265. if (values == target):
  266. print ('ReplaceAnyList_replaced_floats')
  267. target = ['one','2','three','0x4']
  268. testObject.mutate_any_container(target)
  269. values = testObject.access_any_container()
  270. if (values == target):
  271. print ('AccessAnyList_matching_strings')
  272. target.reverse()
  273. testObject.mutate_any_container(target)
  274. values = testObject.access_any_container()
  275. if (values == target):
  276. print ('ReplaceAnyList_strings')
  277. )");
  278. }
  279. catch ([[maybe_unused]] const std::exception& e)
  280. {
  281. AZ_Warning("UnitTest", false, "Failed with %s", e.what());
  282. FAIL();
  283. }
  284. e.Deactivate();
  285. EXPECT_EQ(5, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::AccessAnyList)]);
  286. EXPECT_EQ(4, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::ReplaceAnyList)]);
  287. }
  288. TEST_F(PythonReflectAnyContainerTests, AccessReplaceComplexTypes)
  289. {
  290. enum class LogTypes
  291. {
  292. Skip = 0,
  293. AccessAnyList,
  294. ReplaceAnyList,
  295. };
  296. m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
  297. {
  298. if (AzFramework::StringFunc::Equal(window, "python"))
  299. {
  300. if (AzFramework::StringFunc::StartsWith(message, "AccessAnyList"))
  301. {
  302. return aznumeric_cast<int>(LogTypes::AccessAnyList);
  303. }
  304. else if (AzFramework::StringFunc::StartsWith(message, "ReplaceAnyList"))
  305. {
  306. return aznumeric_cast<int>(LogTypes::ReplaceAnyList);
  307. }
  308. }
  309. return aznumeric_cast<int>(LogTypes::Skip);
  310. };
  311. PythonReflectionAnyContainer pythonReflectionAnyContainer;
  312. pythonReflectionAnyContainer.Reflect(m_app.GetBehaviorContext());
  313. pythonReflectionAnyContainer.Reflect(m_app.GetSerializeContext());
  314. Descriptor descriptor;
  315. descriptor.Reflect(m_app.GetBehaviorContext());
  316. descriptor.Reflect(m_app.GetSerializeContext());
  317. AZ::Entity e;
  318. Activate(e);
  319. SimulateEditorBecomingInitialized();
  320. try
  321. {
  322. pybind11::exec(R"(
  323. import azlmbr.test as test
  324. import azlmbr.object
  325. testObject = test.PythonReflectionAnyContainer()
  326. def create_descriptor(s32, u32, scalar, bool_value, string_value):
  327. descriptor = test.Descriptor()
  328. descriptor.s32 = s32
  329. descriptor.u32 = u32
  330. descriptor.scalar = scalar
  331. descriptor.bool_value = bool_value
  332. descriptor.string_value = string_value
  333. return descriptor
  334. def equals_descriptor(lhs, rhs):
  335. return (lhs.s32 == rhs.s32 and
  336. lhs.u32 == rhs.u32 and
  337. lhs.scalar == rhs.scalar and
  338. lhs.bool_value == rhs.bool_value and
  339. lhs.string_value == rhs.string_value)
  340. target = []
  341. target.append(create_descriptor(-1, 2, 3.0, True, 'one'))
  342. target.append(create_descriptor(-2, 3, 4.0, False, '0X2'))
  343. target.append(create_descriptor(-3, 4, 5.0, True, 'T H R E E'))
  344. testObject.mutate_any_container(target)
  345. values = testObject.access_any_container()
  346. if( isinstance(values[0], azlmbr.object.PythonProxyObject) and values[0].typename == 'Descriptor'):
  347. print ('AccessAnyList_matches_descriptor_type')
  348. target.reverse()
  349. testObject.mutate_any_container(target)
  350. values = testObject.access_any_container()
  351. for x in range(0, len(values)):
  352. if ( equals_descriptor(values[x], target[x]) ):
  353. print ('ReplaceAnyList_replaced_descriptors')
  354. )");
  355. }
  356. catch ([[maybe_unused]] const std::exception& e)
  357. {
  358. AZ_Warning("UnitTest", false, "Failed with %s", e.what());
  359. FAIL();
  360. }
  361. e.Deactivate();
  362. EXPECT_EQ(3, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::AccessAnyList)]);
  363. EXPECT_EQ(5, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::ReplaceAnyList)]);
  364. }
  365. TEST_F(PythonReflectAnyContainerTests, CustomTypeTemplates)
  366. {
  367. enum class LogTypes
  368. {
  369. Skip = 0,
  370. Float,
  371. String,
  372. Integer
  373. };
  374. m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
  375. {
  376. if (AzFramework::StringFunc::Equal(window, "python"))
  377. {
  378. if (AzFramework::StringFunc::StartsWith(message, "Float"))
  379. {
  380. return aznumeric_cast<int>(LogTypes::Float);
  381. }
  382. else if (AzFramework::StringFunc::StartsWith(message, "String"))
  383. {
  384. return aznumeric_cast<int>(LogTypes::String);
  385. }
  386. else if (AzFramework::StringFunc::StartsWith(message, "Integer"))
  387. {
  388. return aznumeric_cast<int>(LogTypes::Integer);
  389. }
  390. }
  391. return aznumeric_cast<int>(LogTypes::Skip);
  392. };
  393. CustomTypeHolder customTypeHolder;
  394. customTypeHolder.Reflect(m_app.GetBehaviorContext());
  395. customTypeHolder.Reflect(m_app.GetSerializeContext());
  396. AZ::Entity e;
  397. Activate(e);
  398. SimulateEditorBecomingInitialized();
  399. try
  400. {
  401. pybind11::exec(R"(
  402. import azlmbr.test
  403. import azlmbr.test.template
  404. templateFloat = azlmbr.test.template.CustomTest_MyTemplate_float(40.0 + 2.0)
  405. print('Float - created template with float')
  406. templateString = azlmbr.test.template.CustomTest_MyTemplate_string('forty-two')
  407. print('String - created template with string')
  408. templateInt = azlmbr.test.template.CustomTest_MyTemplate_int(40 + 2)
  409. print('Integer - created template with int')
  410. )");
  411. }
  412. catch ([[maybe_unused]] const std::exception& e)
  413. {
  414. AZ_Error("UnitTest", false, "Failed with %s", e.what());
  415. }
  416. e.Deactivate();
  417. EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Float)]);
  418. EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::String)]);
  419. EXPECT_EQ(1, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::Integer)]);
  420. }
  421. }