You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h

418 lines
17 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Memory/MemoryDriller.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/std/containers/vector.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzTest/AzTest.h>
#include <Nodes/BehaviorContextObjectTestNode.h>
#include <Nodes/Nodeables/SharedDataSlotExample.h>
#include <Nodes/Nodeables/ValuePointerReferenceExample.h>
#include <ScriptCanvas/Core/Graph.h>
#include <ScriptCanvas/Core/SlotConfigurationDefaults.h>
#include <ScriptCanvas/ScriptCanvasGem.h>
#include <ScriptCanvas/SystemComponent.h>
#include "EntityRefTests.h"
#include "ScriptCanvasTestApplication.h"
#include "ScriptCanvasTestBus.h"
#include "ScriptCanvasTestNodes.h"
#include "ScriptCanvasTestUtilities.h"
#define SC_EXPECT_DOUBLE_EQ(candidate, reference) EXPECT_NEAR(candidate, reference, 0.001)
#define SC_EXPECT_FLOAT_EQ(candidate, reference) EXPECT_NEAR(candidate, reference, 0.001f)
namespace ScriptCanvasTests
{
class ScriptCanvasTestFixture
: public ::testing::Test
//, protected NodeAccessor
{
public:
static AZStd::atomic_bool s_asyncOperationActive;
protected:
static ScriptCanvasTests::Application* s_application;
static void SetUpTestCase()
{
s_allocatorSetup.SetupAllocator();
s_asyncOperationActive = false;
if (s_application == nullptr)
{
AZ::ComponentApplication::StartupParameters appStartup;
s_application = aznew ScriptCanvasTests::Application();
{
ScriptCanvasEditor::TraceSuppressionBus::Broadcast(&ScriptCanvasEditor::TraceSuppressionRequests::SuppressPrintf, true);
AZ::ComponentApplication::Descriptor descriptor;
descriptor.m_enableDrilling = false;
descriptor.m_useExistingAllocator = true;
AZ::DynamicModuleDescriptor dynamicModuleDescriptor;
dynamicModuleDescriptor.m_dynamicLibraryPath = "GraphCanvas.Editor";
descriptor.m_modules.push_back(dynamicModuleDescriptor);
dynamicModuleDescriptor.m_dynamicLibraryPath = "ScriptCanvas.Editor";
descriptor.m_modules.push_back(dynamicModuleDescriptor);
dynamicModuleDescriptor.m_dynamicLibraryPath = "ExpressionEvaluation";
descriptor.m_modules.push_back(dynamicModuleDescriptor);
dynamicModuleDescriptor.m_dynamicLibraryPath = "ScriptEvents";
descriptor.m_modules.push_back(dynamicModuleDescriptor);
s_application->Start(descriptor, appStartup);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
ScriptCanvasEditor::TraceSuppressionBus::Broadcast(&ScriptCanvasEditor::TraceSuppressionRequests::SuppressPrintf, false);
}
}
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO, "SC unit tests require filehandling");
s_setupSucceeded = fileIO->GetAlias("@engroot@") != nullptr;
AZ::TickBus::AllowFunctionQueuing(true);
auto m_serializeContext = s_application->GetSerializeContext();
auto m_behaviorContext = s_application->GetBehaviorContext();
ScriptCanvasTesting::Reflect(m_serializeContext);
ScriptCanvasTesting::Reflect(m_behaviorContext);
ScriptCanvasTestingNodes::BehaviorContextObjectTest::Reflect(m_serializeContext);
ScriptCanvasTestingNodes::BehaviorContextObjectTest::Reflect(m_behaviorContext);
::Nodes::InputMethodSharedDataSlotExampleNode::Reflect(m_serializeContext);
::Nodes::InputMethodSharedDataSlotExampleNode::Reflect(m_behaviorContext);
::Nodes::BranchMethodSharedDataSlotExampleNode::Reflect(m_serializeContext);
::Nodes::BranchMethodSharedDataSlotExampleNode::Reflect(m_behaviorContext);
::Nodes::ReturnTypeExampleNode::Reflect(m_serializeContext);
::Nodes::ReturnTypeExampleNode::Reflect(m_behaviorContext);
::Nodes::InputTypeExampleNode::Reflect(m_serializeContext);
::Nodes::InputTypeExampleNode::Reflect(m_behaviorContext);
::Nodes::BranchInputTypeExampleNode::Reflect(m_serializeContext);
::Nodes::BranchInputTypeExampleNode::Reflect(m_behaviorContext);
::Nodes::PropertyExampleNode::Reflect(m_serializeContext);
::Nodes::PropertyExampleNode::Reflect(m_behaviorContext);
TestNodeableObject::Reflect(m_serializeContext);
TestNodeableObject::Reflect(m_behaviorContext);
ScriptUnitTestEventHandler::Reflect(m_serializeContext);
ScriptUnitTestEventHandler::Reflect(m_behaviorContext);
}
static void TearDownTestCase()
{
// don't hang on to dangling assets
AZ::Data::AssetManager::Instance().DispatchEvents();
if (s_application)
{
s_application->Stop();
delete s_application;
s_application = nullptr;
}
s_allocatorSetup.TeardownAllocator();
}
template<class T>
void RegisterComponentDescriptor()
{
AZ::ComponentDescriptor* descriptor = T::CreateDescriptor();
auto insertResult = m_descriptors.insert(descriptor);
if (insertResult.second)
{
GetApplication()->RegisterComponentDescriptor(descriptor);
}
}
void SetUp() override
{
ASSERT_TRUE(s_setupSucceeded) << "ScriptCanvasTestFixture set up failed, unit tests can't work properly";
m_serializeContext = s_application->GetSerializeContext();
m_behaviorContext = s_application->GetBehaviorContext();
AZ_Assert(AZ::IO::FileIOBase::GetInstance(), "File IO was not properly installed");
RegisterComponentDescriptor<TestNodes::TestResult>();
RegisterComponentDescriptor<TestNodes::ConfigurableUnitTestNode>();
m_numericVectorType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<AZStd::vector<ScriptCanvas::Data::NumberType>>());
m_stringToNumberMapType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<AZStd::unordered_map<ScriptCanvas::Data::StringType, ScriptCanvas::Data::NumberType>>());
m_dataSlotConfigurationType = ScriptCanvas::Data::Type::BehaviorContextObject(azrtti_typeid<ScriptCanvas::DataSlotConfiguration>());
}
void TearDown() override
{
delete m_graph;
m_graph = nullptr;
ASSERT_TRUE(s_setupSucceeded) << "ScriptCanvasTestFixture set up failed, unit tests can't work properly";
for (AZ::ComponentDescriptor* componentDescriptor : m_descriptors)
{
GetApplication()->UnregisterComponentDescriptor(componentDescriptor);
}
m_descriptors.clear();
}
ScriptCanvas::Graph* CreateGraph()
{
if (m_graph == nullptr)
{
m_graph = aznew ScriptCanvas::Graph();
m_graph->Init();
}
return m_graph;
}
TestNodes::ConfigurableUnitTestNode* CreateConfigurableNode(AZStd::string entityName = "ConfigurableNodeEntity")
{
AZ::Entity* configurableNodeEntity = new AZ::Entity(entityName.c_str());
auto configurableNode = configurableNodeEntity->CreateComponent<TestNodes::ConfigurableUnitTestNode>();
if (m_graph == nullptr)
{
CreateGraph();
}
configurableNodeEntity->Init();
m_graph->AddNode(configurableNodeEntity->GetId());
return configurableNode;
}
void ReportErrors(ScriptCanvas::Graph* graph, bool expectErrors = false, bool expectIrrecoverableErrors = false)
{
AZ_UNUSED(graph);
AZ_UNUSED(expectErrors);
AZ_UNUSED(expectIrrecoverableErrors);
}
void TestConnectionBetween(ScriptCanvas::Endpoint sourceEndpoint, ScriptCanvas::Endpoint targetEndpoint, bool isValid = true)
{
EXPECT_EQ(m_graph->CanConnectionExistBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
EXPECT_EQ(m_graph->CanConnectionExistBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
EXPECT_EQ(m_graph->CanCreateConnectionBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
EXPECT_EQ(m_graph->CanCreateConnectionBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
if (isValid)
{
EXPECT_TRUE(m_graph->ConnectByEndpoint(sourceEndpoint, targetEndpoint));
}
}
void TestIsConnectionPossible(ScriptCanvas::Endpoint sourceEndpoint, ScriptCanvas::Endpoint targetEndpoint, bool isValid = true)
{
EXPECT_EQ(m_graph->CanConnectionExistBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
EXPECT_EQ(m_graph->CanConnectionExistBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
EXPECT_EQ(m_graph->CanCreateConnectionBetween(sourceEndpoint, targetEndpoint).IsSuccess(), isValid);
EXPECT_EQ(m_graph->CanCreateConnectionBetween(targetEndpoint, sourceEndpoint).IsSuccess(), isValid);
}
void CreateExecutionFlowBetween(AZStd::vector<TestNodes::ConfigurableUnitTestNode*> unitTestNodes)
{
ScriptCanvas::Slot* previousOutSlot = nullptr;
for (TestNodes::ConfigurableUnitTestNode* testNode : unitTestNodes)
{
{
ScriptCanvas::ExecutionSlotConfiguration inputSlot = ScriptCanvas::CommonSlots::GeneralInSlot();
ScriptCanvas::Slot* slot = testNode->AddTestingSlot(inputSlot);
if (slot && previousOutSlot)
{
TestConnectionBetween(previousOutSlot->GetEndpoint(), slot->GetEndpoint());
}
}
{
ScriptCanvas::ExecutionSlotConfiguration outputSlot = ScriptCanvas::CommonSlots::GeneralOutSlot();
previousOutSlot = testNode->AddTestingSlot(outputSlot);
}
}
}
AZStd::vector< ScriptCanvas::Data::Type > GetContainerDataTypes() const
{
return { m_numericVectorType, m_stringToNumberMapType };
}
ScriptCanvas::Data::Type GetRandomContainerType() const
{
AZStd::vector< ScriptCanvas::Data::Type > containerTypes = GetContainerDataTypes();
// We have no types to randomize. Just return.
if (containerTypes.empty())
{
return m_numericVectorType;
}
int randomIndex = rand() % containerTypes.size();
ScriptCanvas::Data::Type randomType = containerTypes[randomIndex];
AZ_TracePrintf("ScriptCanvasTestFixture", "RandomContainerType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
return randomType;
}
AZStd::vector< ScriptCanvas::Data::Type > GetPrimitiveTypes() const
{
return{
ScriptCanvas::Data::Type::AABB(),
ScriptCanvas::Data::Type::Boolean(),
ScriptCanvas::Data::Type::Color(),
ScriptCanvas::Data::Type::CRC(),
ScriptCanvas::Data::Type::EntityID(),
ScriptCanvas::Data::Type::Matrix3x3(),
ScriptCanvas::Data::Type::Matrix4x4(),
ScriptCanvas::Data::Type::Number(),
ScriptCanvas::Data::Type::OBB(),
ScriptCanvas::Data::Type::Plane(),
ScriptCanvas::Data::Type::Quaternion(),
ScriptCanvas::Data::Type::String(),
ScriptCanvas::Data::Type::Transform(),
ScriptCanvas::Data::Type::Vector2(),
ScriptCanvas::Data::Type::Vector3(),
ScriptCanvas::Data::Type::Vector4()
};
}
ScriptCanvas::Data::Type GetRandomPrimitiveType() const
{
AZStd::vector< ScriptCanvas::Data::Type > primitiveTypes = GetPrimitiveTypes();
// We have no types to randomize. Just return.
if (primitiveTypes.empty())
{
return ScriptCanvas::Data::Type::Number();
}
int randomIndex = rand() % primitiveTypes.size();
ScriptCanvas::Data::Type randomType = primitiveTypes[randomIndex];
AZ_TracePrintf("ScriptCanvasTestFixture", "RandomPrimitiveType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
return randomType;
}
AZStd::vector< ScriptCanvas::Data::Type > GetBehaviorObjectTypes() const
{
return {
m_dataSlotConfigurationType
};
}
ScriptCanvas::Data::Type GetRandomObjectType() const
{
AZStd::vector< ScriptCanvas::Data::Type > objectTypes = GetBehaviorObjectTypes();
// We have no types to randomize. Just return.
if (objectTypes.empty())
{
return m_dataSlotConfigurationType;
}
int randomIndex = rand() % objectTypes.size();
ScriptCanvas::Data::Type randomType = objectTypes[randomIndex];
AZ_TracePrintf("ScriptCanvasTestFixture", "RandomObjectType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
return randomType;
}
AZStd::vector< ScriptCanvas::Data::Type > GetTypes() const
{
auto primitives = GetPrimitiveTypes();
auto containers = GetContainerDataTypes();
auto objects = GetBehaviorObjectTypes();
primitives.reserve(containers.size() + objects.size());
primitives.insert(primitives.end(), containers.begin(), containers.end());
primitives.insert(primitives.end(), objects.begin(), objects.end());
return primitives;
}
ScriptCanvas::Data::Type GetRandomType() const
{
AZStd::vector< ScriptCanvas::Data::Type > types = GetTypes();
// We have no types to randomize. Just return.
if (types.empty())
{
return m_dataSlotConfigurationType;
}
int randomIndex = rand() % types.size();
ScriptCanvas::Data::Type randomType = types[randomIndex];
AZ_TracePrintf("ScriptCanvasTestFixture", "RandomType: %s\n", randomType.GetAZType().ToString<AZStd::string>().c_str());
return randomType;
}
AZStd::string GenerateSlotName()
{
AZStd::string slotName = AZStd::string::format("Slot %i", m_slotCounter);
++m_slotCounter;
return slotName;
}
AZ::SerializeContext* m_serializeContext;
AZ::BehaviorContext* m_behaviorContext;
UnitTestEntityContext m_entityContext;
// Really big(visually) data types just storing here for ease of use in situations.
ScriptCanvas::Data::Type m_numericVectorType;
ScriptCanvas::Data::Type m_stringToNumberMapType;
ScriptCanvas::Data::Type m_dataSlotConfigurationType;
ScriptCanvas::Graph* m_graph = nullptr;
int m_slotCounter = 0;
AZStd::unordered_map< AZ::EntityId, AZ::Entity* > m_entityMap;
protected:
static ScriptCanvasTests::Application* GetApplication() { return s_application; }
private:
static UnitTest::AllocatorsBase s_allocatorSetup;
static bool s_setupSucceeded;
AZStd::unordered_set< AZ::ComponentDescriptor* > m_descriptors;
};
}