ScriptCanvasTestFixture.h 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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. #pragma once
  9. #include <AzCore/Asset/AssetManagerComponent.h>
  10. #include <AzCore/Component/ComponentApplicationBus.h>
  11. #include <AzCore/IO/FileIO.h>
  12. #include <AzCore/UnitTest/TestTypes.h>
  13. #include <AzCore/UserSettings/UserSettingsComponent.h>
  14. #include <AzCore/std/containers/vector.h>
  15. #include <AzFramework/IO/LocalFileIO.h>
  16. #include <AzTest/AzTest.h>
  17. #include <TestAutoGenFunctionRegistry.generated.h>
  18. #include <TestAutoGenNodeableRegistry.generated.h>
  19. #include <Nodes/BehaviorContextObjectTestNode.h>
  20. #include <Nodes/TestAutoGenFunctions.h>
  21. #include <ScriptCanvas/Components/EditorGraph.h>
  22. #include <ScriptCanvas/Core/Graph.h>
  23. #include <ScriptCanvas/Core/SlotConfigurationDefaults.h>
  24. #include <ScriptCanvas/ScriptCanvasGem.h>
  25. #include <ScriptCanvas/SystemComponent.h>
  26. #include <ScriptCanvas/Variable/GraphVariableManagerComponent.h>
  27. #include "EntityRefTests.h"
  28. #include "ScriptCanvasTestApplication.h"
  29. #include "ScriptCanvasTestBus.h"
  30. #include "ScriptCanvasTestNodes.h"
  31. #include "ScriptCanvasTestUtilities.h"
  32. #define SC_EXPECT_DOUBLE_EQ(candidate, reference) EXPECT_NEAR(candidate, reference, 0.001)
  33. #define SC_EXPECT_FLOAT_EQ(candidate, reference) EXPECT_NEAR(candidate, reference, 0.001f)
  34. REGISTER_SCRIPTCANVAS_AUTOGEN_FUNCTION(ScriptCanvasTestingEditorStatic);
  35. REGISTER_SCRIPTCANVAS_AUTOGEN_NODEABLE(ScriptCanvasTestingEditorStatic);
  36. namespace ScriptCanvasTests
  37. {
  38. class ScriptCanvasTestFixture
  39. : public ::testing::Test
  40. //, protected NodeAccessor
  41. {
  42. public:
  43. static AZStd::atomic_bool s_asyncOperationActive;
  44. protected:
  45. static ScriptCanvasTests::Application* s_application;
  46. static void SetUpTestCase()
  47. {
  48. s_asyncOperationActive = false;
  49. if (s_application == nullptr)
  50. {
  51. AZ::ComponentApplication::StartupParameters appStartup;
  52. s_application = aznew ScriptCanvasTests::Application();
  53. {
  54. ScriptCanvasEditor::TraceSuppressionBus::Broadcast(&ScriptCanvasEditor::TraceSuppressionRequests::SuppressPrintf, true);
  55. AZ::ComponentApplication::Descriptor descriptor;
  56. descriptor.m_useExistingAllocator = true;
  57. AZ::DynamicModuleDescriptor dynamicModuleDescriptor;
  58. dynamicModuleDescriptor.m_dynamicLibraryPath = "GraphCanvas.Editor";
  59. descriptor.m_modules.push_back(dynamicModuleDescriptor);
  60. dynamicModuleDescriptor.m_dynamicLibraryPath = "ScriptCanvas.Editor";
  61. descriptor.m_modules.push_back(dynamicModuleDescriptor);
  62. dynamicModuleDescriptor.m_dynamicLibraryPath = "ExpressionEvaluation";
  63. descriptor.m_modules.push_back(dynamicModuleDescriptor);
  64. dynamicModuleDescriptor.m_dynamicLibraryPath = "ScriptEvents";
  65. descriptor.m_modules.push_back(dynamicModuleDescriptor);
  66. s_application->Start(descriptor, appStartup);
  67. // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
  68. // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
  69. // in the unit tests.
  70. AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
  71. ScriptCanvasEditor::TraceSuppressionBus::Broadcast(&ScriptCanvasEditor::TraceSuppressionRequests::SuppressPrintf, false);
  72. }
  73. }
  74. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  75. AZ_Assert(fileIO, "SC unit tests require filehandling");
  76. s_setupSucceeded = fileIO->GetAlias("@engroot@") != nullptr;
  77. // Set the @gemroot:<gem-name> alias for active gems
  78. auto settingsRegistry = AZ::SettingsRegistry::Get();
  79. if (settingsRegistry)
  80. {
  81. AZ::Test::AddActiveGem("ScriptCanvasTesting", *settingsRegistry, fileIO);
  82. AZ::Test::AddActiveGem("GraphCanvas", *settingsRegistry, fileIO);
  83. AZ::Test::AddActiveGem("ScriptCanvas", *settingsRegistry, fileIO);
  84. AZ::Test::AddActiveGem("ScriptEvents", *settingsRegistry, fileIO);
  85. AZ::Test::AddActiveGem("ExpressionEvaluation", *settingsRegistry, fileIO);
  86. }
  87. AZ::TickBus::AllowFunctionQueuing(true);
  88. auto m_serializeContext = s_application->GetSerializeContext();
  89. auto m_behaviorContext = s_application->GetBehaviorContext();
  90. for (AZ::ReflectContext* context :
  91. {static_cast<AZ::ReflectContext*>(m_serializeContext), static_cast<AZ::ReflectContext*>(m_behaviorContext)})
  92. {
  93. ScriptCanvasTesting::Reflect(context);
  94. ScriptCanvasTestingNodes::BehaviorContextObjectTest::Reflect(context);
  95. TestNodeableObject::Reflect(context);
  96. TestBaseClass::Reflect(context);
  97. TestSubClass::Reflect(context);
  98. ScriptUnitTestEventHandler::Reflect(context);
  99. }
  100. }
  101. static void TearDownTestCase()
  102. {
  103. ScriptCanvas::AutoGenRegistryManager::GetInstance()->UnregisterRegistry("ScriptCanvasTestingEditorStaticFunctionRegistry");
  104. ScriptCanvas::AutoGenRegistryManager::GetInstance()->UnregisterRegistry("ScriptCanvasTestingEditorStaticNodeableRegistry");
  105. // don't hang on to dangling assets
  106. AZ::Data::AssetManager::Instance().DispatchEvents();
  107. if (s_application)
  108. {
  109. s_application->Stop();
  110. delete s_application;
  111. s_application = nullptr;
  112. }
  113. s_leakDetection.CheckAllocatorsForLeaks();
  114. }
  115. template<class T>
  116. void RegisterComponentDescriptor()
  117. {
  118. AZ::ComponentDescriptor* descriptor = T::CreateDescriptor();
  119. auto insertResult = m_descriptors.insert(descriptor);
  120. if (insertResult.second)
  121. {
  122. GetApplication()->RegisterComponentDescriptor(descriptor);
  123. }
  124. }
  125. void SetUp() override
  126. {
  127. ASSERT_TRUE(s_setupSucceeded) << "ScriptCanvasTestFixture set up failed, unit tests can't work properly";
  128. m_serializeContext = s_application->GetSerializeContext();
  129. m_behaviorContext = s_application->GetBehaviorContext();
  130. AZ_Assert(AZ::IO::FileIOBase::GetInstance(), "File IO was not properly installed");
  131. RegisterComponentDescriptor<TestNodes::TestResult>();
  132. RegisterComponentDescriptor<TestNodes::ConfigurableUnitTestNode>();
  133. m_numericVectorType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<AZStd::vector<ScriptCanvas::Data::NumberType>>());
  134. m_stringToNumberMapType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<AZStd::unordered_map<ScriptCanvas::Data::StringType, ScriptCanvas::Data::NumberType>>());
  135. m_dataSlotConfigurationType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<ScriptCanvas::DataSlotConfiguration>());
  136. m_baseClassType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<TestBaseClass>());
  137. m_subClassType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<TestSubClass>());
  138. }
  139. void TearDown() override
  140. {
  141. delete m_graph;
  142. m_graph = nullptr;
  143. ASSERT_TRUE(s_setupSucceeded) << "ScriptCanvasTestFixture set up failed, unit tests can't work properly";
  144. for (AZ::ComponentDescriptor* componentDescriptor : m_descriptors)
  145. {
  146. GetApplication()->UnregisterComponentDescriptor(componentDescriptor);
  147. }
  148. m_descriptors.clear();
  149. }
  150. ScriptCanvas::Graph* CreateGraph()
  151. {
  152. AZ_Assert(!m_graph, "Only one graph should be created per test.");
  153. m_graph = aznew ScriptCanvas::Graph();
  154. m_graph->Init();
  155. return m_graph;
  156. }
  157. ScriptCanvasEditor::EditorGraph* CreateEditorGraph()
  158. {
  159. AZ_Assert(!m_graph, "Only one graph should be created per test.");
  160. m_graph = aznew ScriptCanvasEditor::EditorGraph();
  161. m_graph->Init();
  162. return static_cast<ScriptCanvasEditor::EditorGraph*>(m_graph);
  163. }
  164. TestNodes::ConfigurableUnitTestNode* CreateConfigurableNode(AZStd::string entityName = "ConfigurableNodeEntity")
  165. {
  166. AZ::Entity* configurableNodeEntity = new AZ::Entity(entityName.c_str());
  167. auto configurableNode = configurableNodeEntity->CreateComponent<TestNodes::ConfigurableUnitTestNode>();
  168. AZ_Assert(m_graph, "A graph must be created before any nodes are created.");
  169. if (!m_graph)
  170. {
  171. return nullptr;
  172. }
  173. ScriptCanvas::ScriptCanvasId scriptCanvasId = m_graph->GetScriptCanvasId();
  174. configurableNodeEntity->CreateComponent<ScriptCanvas::GraphVariableManagerComponent>(scriptCanvasId);
  175. configurableNodeEntity->Init();
  176. m_graph->Activate();
  177. m_graph->AddNode(configurableNodeEntity->GetId());
  178. return configurableNode;
  179. }
  180. void ReportErrors(ScriptCanvas::Graph* graph, bool expectErrors = false, bool expectIrrecoverableErrors = false)
  181. {
  182. AZ_UNUSED(graph);
  183. AZ_UNUSED(expectErrors);
  184. AZ_UNUSED(expectIrrecoverableErrors);
  185. }
  186. void TestConnectionBetween(ScriptCanvas::Endpoint sourceEndpoint, ScriptCanvas::Endpoint targetEndpoint, bool isValid = true)
  187. {
  188. EXPECT_EQ(m_graph->CanConnectionExistBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
  189. EXPECT_EQ(m_graph->CanConnectionExistBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
  190. EXPECT_EQ(m_graph->CanCreateConnectionBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
  191. EXPECT_EQ(m_graph->CanCreateConnectionBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
  192. if (isValid)
  193. {
  194. EXPECT_TRUE(m_graph->ConnectByEndpoint(sourceEndpoint, targetEndpoint));
  195. }
  196. }
  197. void TestIsConnectionPossible(ScriptCanvas::Endpoint sourceEndpoint, ScriptCanvas::Endpoint targetEndpoint, bool isValid = true)
  198. {
  199. EXPECT_EQ(m_graph->CanConnectionExistBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
  200. EXPECT_EQ(m_graph->CanConnectionExistBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
  201. EXPECT_EQ(m_graph->CanCreateConnectionBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
  202. EXPECT_EQ(m_graph->CanCreateConnectionBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
  203. }
  204. // Test if there is an existing connection between the provided endpoints
  205. void TestIsConnectionBetween(const ScriptCanvas::Endpoint& sourceEndpoint, const ScriptCanvas::Endpoint& targetEndpoint, bool isValid = true)
  206. {
  207. AZ::Entity* ent;
  208. EXPECT_EQ(m_graph->FindConnection(ent, sourceEndpoint, targetEndpoint), isValid);
  209. }
  210. // Tests implicit connections between nodes by connecting and disconnecting every data source and data slot while checking to make
  211. // sure that a connection is maintained between the source and target execution slots as long as at least one set of source and target
  212. // data slots are connected, and that no other execution out slots are connected to the target execution slot
  213. void TestAllImplicitConnections(
  214. ScriptCanvasEditor::EditorGraph* editorGraph,
  215. AZStd::vector<ScriptCanvas::Endpoint> sourceDataSlots,
  216. AZStd::vector<ScriptCanvas::Endpoint> targetDataSlots,
  217. ScriptCanvas::Endpoint sourceExecSlot,
  218. ScriptCanvas::Endpoint targetExecSlot,
  219. AZStd::vector<ScriptCanvas::Endpoint> allExecutionOutSlots)
  220. {
  221. // Connect all of the data slots
  222. for (auto sourceDataSlot : sourceDataSlots)
  223. {
  224. for (auto targetDataSlot : targetDataSlots)
  225. {
  226. TestConnectionBetween(sourceDataSlot, targetDataSlot, true);
  227. editorGraph->UpdateCorrespondingImplicitConnection(sourceDataSlot, targetDataSlot);
  228. // Ensure the implicit connection exists
  229. TestIsConnectionBetween(sourceExecSlot, targetExecSlot, true);
  230. for (auto otherExecSlot : allExecutionOutSlots)
  231. {
  232. if (otherExecSlot.GetSlotId() != sourceExecSlot.GetSlotId())
  233. {
  234. // Ensure that no implicit connections exist between any of the other execution out slots and the target
  235. // execution slot
  236. TestIsConnectionBetween(otherExecSlot, targetExecSlot, false);
  237. }
  238. }
  239. }
  240. }
  241. // Disconnect all of the data slots
  242. for (int i = 0; i < sourceDataSlots.size(); i++)
  243. {
  244. for (int j = 0; j < targetDataSlots.size(); j++)
  245. {
  246. editorGraph->DisconnectByEndpoint(sourceDataSlots[i], targetDataSlots[j]);
  247. editorGraph->UpdateCorrespondingImplicitConnection(sourceDataSlots[i], targetDataSlots[j]);
  248. // Ensure the implicit connection exists only if this is not the last data connection. If it is, then ensure that
  249. // no implicit connection exists
  250. TestIsConnectionBetween(sourceExecSlot, targetExecSlot, (i < sourceDataSlots.size() - 1 || j < targetDataSlots.size() - 1));
  251. for (auto otherExecSlot : allExecutionOutSlots)
  252. {
  253. if (otherExecSlot.GetSlotId() != sourceExecSlot.GetSlotId())
  254. {
  255. // Ensure that no implicit connections exist between any of the other execution out slots and the target
  256. // execution slot
  257. TestIsConnectionBetween(otherExecSlot, targetExecSlot, false);
  258. }
  259. }
  260. }
  261. }
  262. }
  263. void CreateExecutionFlowBetween(AZStd::vector<TestNodes::ConfigurableUnitTestNode*> unitTestNodes)
  264. {
  265. ScriptCanvas::Slot* previousOutSlot = nullptr;
  266. for (TestNodes::ConfigurableUnitTestNode* testNode : unitTestNodes)
  267. {
  268. {
  269. ScriptCanvas::ExecutionSlotConfiguration inputSlot = ScriptCanvas::CommonSlots::GeneralInSlot();
  270. ScriptCanvas::Slot* slot = testNode->AddTestingSlot(inputSlot);
  271. if (slot && previousOutSlot)
  272. {
  273. TestConnectionBetween(previousOutSlot->GetEndpoint(), slot->GetEndpoint());
  274. }
  275. }
  276. {
  277. ScriptCanvas::ExecutionSlotConfiguration outputSlot = ScriptCanvas::CommonSlots::GeneralOutSlot();
  278. previousOutSlot = testNode->AddTestingSlot(outputSlot);
  279. }
  280. }
  281. }
  282. AZStd::vector< ScriptCanvas::Data::Type > GetContainerDataTypes() const
  283. {
  284. return { m_numericVectorType, m_stringToNumberMapType };
  285. }
  286. ScriptCanvas::Data::Type GetRandomContainerType() const
  287. {
  288. AZStd::vector< ScriptCanvas::Data::Type > containerTypes = GetContainerDataTypes();
  289. // We have no types to randomize. Just return.
  290. if (containerTypes.empty())
  291. {
  292. return m_numericVectorType;
  293. }
  294. int randomIndex = rand() % containerTypes.size();
  295. ScriptCanvas::Data::Type randomType = containerTypes[randomIndex];
  296. AZ_TracePrintf("ScriptCanvasTestFixture", "RandomContainerType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
  297. return randomType;
  298. }
  299. AZStd::vector< ScriptCanvas::Data::Type > GetPrimitiveTypes() const
  300. {
  301. return{
  302. ScriptCanvas::Data::Type::AABB(),
  303. ScriptCanvas::Data::Type::Boolean(),
  304. ScriptCanvas::Data::Type::Color(),
  305. ScriptCanvas::Data::Type::CRC(),
  306. ScriptCanvas::Data::Type::EntityID(),
  307. ScriptCanvas::Data::Type::Matrix3x3(),
  308. ScriptCanvas::Data::Type::Matrix4x4(),
  309. ScriptCanvas::Data::Type::Number(),
  310. ScriptCanvas::Data::Type::OBB(),
  311. ScriptCanvas::Data::Type::Plane(),
  312. ScriptCanvas::Data::Type::Quaternion(),
  313. ScriptCanvas::Data::Type::String(),
  314. ScriptCanvas::Data::Type::Transform(),
  315. ScriptCanvas::Data::Type::Vector2(),
  316. ScriptCanvas::Data::Type::Vector3(),
  317. ScriptCanvas::Data::Type::Vector4()
  318. };
  319. }
  320. ScriptCanvas::Data::Type GetRandomPrimitiveType() const
  321. {
  322. AZStd::vector< ScriptCanvas::Data::Type > primitiveTypes = GetPrimitiveTypes();
  323. // We have no types to randomize. Just return.
  324. if (primitiveTypes.empty())
  325. {
  326. return ScriptCanvas::Data::Type::Number();
  327. }
  328. int randomIndex = rand() % primitiveTypes.size();
  329. ScriptCanvas::Data::Type randomType = primitiveTypes[randomIndex];
  330. AZ_TracePrintf("ScriptCanvasTestFixture", "RandomPrimitiveType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
  331. return randomType;
  332. }
  333. AZStd::vector< ScriptCanvas::Data::Type > GetBehaviorObjectTypes() const
  334. {
  335. return {
  336. m_dataSlotConfigurationType
  337. };
  338. }
  339. ScriptCanvas::Data::Type GetRandomObjectType() const
  340. {
  341. AZStd::vector< ScriptCanvas::Data::Type > objectTypes = GetBehaviorObjectTypes();
  342. // We have no types to randomize. Just return.
  343. if (objectTypes.empty())
  344. {
  345. return m_dataSlotConfigurationType;
  346. }
  347. int randomIndex = rand() % objectTypes.size();
  348. ScriptCanvas::Data::Type randomType = objectTypes[randomIndex];
  349. AZ_TracePrintf("ScriptCanvasTestFixture", "RandomObjectType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
  350. return randomType;
  351. }
  352. AZStd::vector< ScriptCanvas::Data::Type > GetTypes() const
  353. {
  354. auto primitives = GetPrimitiveTypes();
  355. auto containers = GetContainerDataTypes();
  356. auto objects = GetBehaviorObjectTypes();
  357. primitives.reserve(containers.size() + objects.size());
  358. primitives.insert(primitives.end(), containers.begin(), containers.end());
  359. primitives.insert(primitives.end(), objects.begin(), objects.end());
  360. return primitives;
  361. }
  362. ScriptCanvas::Data::Type GetRandomType() const
  363. {
  364. AZStd::vector< ScriptCanvas::Data::Type > types = GetTypes();
  365. // We have no types to randomize. Just return.
  366. if (types.empty())
  367. {
  368. return m_dataSlotConfigurationType;
  369. }
  370. int randomIndex = rand() % types.size();
  371. ScriptCanvas::Data::Type randomType = types[randomIndex];
  372. AZ_TracePrintf("ScriptCanvasTestFixture", "RandomType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
  373. return randomType;
  374. }
  375. AZStd::string GenerateSlotName()
  376. {
  377. AZStd::string slotName = AZStd::string::format("Slot %i", m_slotCounter);
  378. ++m_slotCounter;
  379. return slotName;
  380. }
  381. AZ::SerializeContext* m_serializeContext;
  382. AZ::BehaviorContext* m_behaviorContext;
  383. UnitTestEntityContext m_entityContext;
  384. // Really big(visually) data types just storing here for ease of use in situations.
  385. ScriptCanvas::Data::Type m_numericVectorType;
  386. ScriptCanvas::Data::Type m_stringToNumberMapType;
  387. ScriptCanvas::Data::Type m_dataSlotConfigurationType;
  388. ScriptCanvas::Data::Type m_baseClassType;
  389. ScriptCanvas::Data::Type m_subClassType;
  390. ScriptCanvas::Graph* m_graph = nullptr;
  391. int m_slotCounter = 0;
  392. AZStd::unordered_map< AZ::EntityId, AZ::Entity* > m_entityMap;
  393. protected:
  394. static ScriptCanvasTests::Application* GetApplication() { return s_application; }
  395. private:
  396. static bool s_setupSucceeded;
  397. static inline UnitTest::LeakDetectionBase s_leakDetection{};
  398. AZStd::unordered_set< AZ::ComponentDescriptor* > m_descriptors;
  399. };
  400. }