ScriptCanvasTestFixture.h 20 KB

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