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/GraphModel/Code/Tests/GraphModelIntegrationTest.cpp

370 lines
18 KiB
C++

/**
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// AZ
#include <AzCore/Debug/StackTracer.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzTest/AzTest.h>
// Graph Canvas
#include <GraphCanvas/Components/Nodes/NodeBus.h>
#include <GraphCanvas/Components/Slots/SlotBus.h>
#include <GraphCanvas/Components/Slots/Data/DataSlotBus.h>
#include <GraphCanvas/Editor/EditorTypes.h>
#include <GraphCanvas/GraphCanvasBus.h>
// GraphModel
#include <GraphModel/GraphModelBus.h>
// GraphModel test harness and minimum setup for a custom GraphModel instance
#include <Tests/TestEnvironment.h>
// These tests rely on this test environment to setup the necessary
// mocked GraphCanvas buses and system components
AZ_UNIT_TEST_HOOK(new GraphModelIntegrationTest::GraphModelTestEnvironment);
namespace GraphModelIntegrationTest
{
/**
* Used for testing the GraphModelIntegration namespace, which relies on GraphCanvasWidgets and Qt.
* NOTE: These tests rely on being run in the GraphModelTestEnvironment, which sets up the
* necessary mocked GraphCanvas buses and system components.
*/
class GraphModelIntegrationTests
: public ::testing::Test
, public UnitTest::TraceBusRedirector
{
protected:
void SetUp() override
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
// Create our test graph context
m_graphContext = AZStd::make_shared<TestGraphContext>();
// Create a new node graph
m_graph = AZStd::make_shared<GraphModel::Graph>(m_graphContext);
// Create a new scene for the graph
AZ::Entity* scene = nullptr;
GraphModelIntegration::GraphManagerRequestBus::BroadcastResult(scene, &GraphModelIntegration::GraphManagerRequests::CreateScene, m_graph, NODE_GRAPH_TEST_EDITOR_ID);
m_scene.reset(scene);
m_sceneId = m_scene->GetId();
}
void TearDown() override
{
// Release shared pointers that were setup
m_graphContext.reset();
m_graph.reset();
m_scene.reset();
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
AZStd::shared_ptr<TestGraphContext> m_graphContext = nullptr;
GraphModel::GraphPtr m_graph = nullptr;
AZStd::unique_ptr<AZ::Entity> m_scene = nullptr;
AZ::EntityId m_sceneId;
};
TEST_F(GraphModelIntegrationTests, NodeAddedToScene)
{
// Create our test node
GraphModel::NodePtr testNode = AZStd::make_shared<TestNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// Make sure the correct node was added to the scene
GraphModel::NodePtrList nodeList;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeList, m_sceneId, &GraphModelIntegration::GraphControllerRequests::GetNodes);
EXPECT_EQ(nodeList.size(), 1);
EXPECT_EQ(nodeList[0], testNode);
}
/**
* Make sure the data type of the slot on a node is set properly. There was a new DataSlotConfiguration added
* in GraphCanvas, which the GraphModel implementation hadn't previously accounted for, resulting in all data slots on
* GraphModel nodes having an invalid data type.
*/
TEST_F(GraphModelIntegrationTests, NodeWithDataSlotHasProperDataType)
{
// Create our test node
GraphModel::NodePtr testNode = AZStd::make_shared<TestNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// Retrieve the data type (string) for the string input slot on our test node
GraphModel::SlotPtr testNodeStringInputSlot = testNode->GetSlot(TEST_STRING_INPUT_ID);
EXPECT_TRUE(testNodeStringInputSlot != nullptr);
GraphModel::DataTypePtr stringDataType = testNodeStringInputSlot->GetDataType();
AZ::Uuid stringDataTypeId = stringDataType->GetTypeUuid();
// Make sure our node has the expected slots
AZStd::vector<AZ::EntityId> slotIds;
GraphCanvas::NodeRequestBus::EventResult(slotIds, nodeId, &GraphCanvas::NodeRequests::GetSlotIds);
EXPECT_EQ(slotIds.size(), 4);
// Make sure the data type of the input string slot on our test node matches the expected data type
AZ::EntityId slotId = slotIds[0];
AZ::Uuid slotDataTypeId;
GraphCanvas::DataSlotRequestBus::EventResult(slotDataTypeId, slotId, &GraphCanvas::DataSlotRequests::GetDataTypeId);
EXPECT_EQ(stringDataTypeId, slotDataTypeId);
}
TEST_F(GraphModelIntegrationTests, GetNodeById)
{
// Create our test node
GraphModel::NodePtr testNode = AZStd::make_shared<TestNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// Test that we can retrieve the expected node by NodeId
GraphModel::NodePtr retrievedNode = nullptr;
GraphModelIntegration::GraphControllerRequestBus::EventResult(retrievedNode, m_sceneId, &GraphModelIntegration::GraphControllerRequests::GetNodeById, nodeId);
EXPECT_EQ(retrievedNode, testNode);
// Test requesting an invalid NodeId returns nullptr
retrievedNode = nullptr;
GraphModelIntegration::GraphControllerRequestBus::EventResult(retrievedNode, m_sceneId, &GraphModelIntegration::GraphControllerRequests::GetNodeById, GraphCanvas::NodeId());
EXPECT_EQ(retrievedNode, nullptr);
// Test requesting a valid NodeId but one that doesn't exist in the scene returns nullptr
retrievedNode = nullptr;
GraphModelIntegration::GraphControllerRequestBus::EventResult(retrievedNode, m_sceneId, &GraphModelIntegration::GraphControllerRequests::GetNodeById, GraphCanvas::NodeId(1234));
EXPECT_EQ(retrievedNode, nullptr);
}
TEST_F(GraphModelIntegrationTests, GetNodesFromGraphNodeIds)
{
// Create a test node
GraphModel::NodePtr testNode = AZStd::make_shared<TestNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// Retrieve Nodes by their NodeId
AZStd::vector<GraphCanvas::NodeId> nodeIds = {
nodeId, // Valid NodeId for a node in the scene
GraphCanvas::NodeId(), // Invalid NodeId
GraphCanvas::NodeId(1234) // Valid NodeId but not in the scene
};
GraphModel::NodePtrList retrievedNodes;
GraphModelIntegration::GraphControllerRequestBus::EventResult(retrievedNodes, m_sceneId, &GraphModelIntegration::GraphControllerRequests::GetNodesFromGraphNodeIds, nodeIds);
EXPECT_EQ(nodeIds.size(), retrievedNodes.size());
// Test the first node in the list should be our valid test node
EXPECT_EQ(retrievedNodes[0], testNode);
// Test the second node should be a nullptr since it was an invalid NodeId
EXPECT_EQ(retrievedNodes[1], nullptr);
// Test the third node should also be a nullptr since it was a valid NodeId but one that doesn't exist in the scene
EXPECT_EQ(retrievedNodes[2], nullptr);
}
TEST_F(GraphModelIntegrationTests, ExtendableSlotsWithDifferentMinimumValues)
{
// Create a node with extendable slots
GraphModel::NodePtr testNode = AZStd::make_shared<ExtendableSlotsNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// The input string extendable slot has a minimum of 0 slots, so there should be none
auto extendableSlots = testNode->GetExtendableSlots(TEST_STRING_INPUT_ID);
int numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_INPUT_ID);
EXPECT_EQ(extendableSlots.size(), 0);
EXPECT_EQ(numExtendableSlots, 0);
// The output string and input event extendable slots both use the default minimum (1)
extendableSlots = testNode->GetExtendableSlots(TEST_STRING_OUTPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_OUTPUT_ID);
EXPECT_EQ(extendableSlots.size(), 1);
EXPECT_EQ(numExtendableSlots, 1);
extendableSlots = testNode->GetExtendableSlots(TEST_EVENT_INPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_EVENT_INPUT_ID);
EXPECT_EQ(extendableSlots.size(), 1);
EXPECT_EQ(numExtendableSlots, 1);
// The output event extendable slot has a minimum of 3 slots
extendableSlots = testNode->GetExtendableSlots(TEST_EVENT_OUTPUT_ID);
EXPECT_EQ(extendableSlots.size(), 3);
}
TEST_F(GraphModelIntegrationTests, ExtendableSlotWithInvalidConfiguration)
{
// Create a node with extendable slots
AZ_TEST_START_TRACE_SUPPRESSION;
GraphModel::NodePtr testNode = AZStd::make_shared<BadNode>(m_graph, m_graphContext);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// The input string extendable slot has a minimum of 5 and a maximum of 1,
// which is an invalid configuration, so there will be no extendable
// slots created for this slot
auto extendableSlots = testNode->GetExtendableSlots(TEST_STRING_INPUT_ID);
int numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_INPUT_ID);
EXPECT_EQ(extendableSlots.size(), 0);
EXPECT_EQ(numExtendableSlots, -1);
}
TEST_F(GraphModelIntegrationTests, AddingExtendableSlotsPastMaximum)
{
// Create a node with extendable slots
GraphModel::NodePtr testNode = AZStd::make_shared<ExtendableSlotsNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// The input string extendable slot has a minimum of 0 slots, so it starts with 0
auto extendableSlots = testNode->GetExtendableSlots(TEST_STRING_INPUT_ID);
int numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_INPUT_ID);
EXPECT_EQ(extendableSlots.size(), 0);
EXPECT_EQ(numExtendableSlots, 0);
// The input string extendable slot has a maximum of 2 slots, so the first add should succeed
GraphModel::SlotPtr firstSlot = testNode->AddExtendedSlot(TEST_STRING_INPUT_ID);
extendableSlots = testNode->GetExtendableSlots(TEST_STRING_INPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_INPUT_ID);
EXPECT_TRUE(firstSlot != nullptr);
EXPECT_EQ(firstSlot->GetName(), TEST_STRING_INPUT_ID);
EXPECT_EQ(extendableSlots.size(), 1);
EXPECT_EQ(numExtendableSlots, 1);
// The input string extendable slot has a maximum of 2 slots, so the second add should also succeed
GraphModel::SlotPtr secondSlot = testNode->AddExtendedSlot(TEST_STRING_INPUT_ID);
extendableSlots = testNode->GetExtendableSlots(TEST_STRING_INPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_INPUT_ID);
EXPECT_TRUE(secondSlot != nullptr);
EXPECT_EQ(secondSlot->GetName(), TEST_STRING_INPUT_ID);
EXPECT_EQ(extendableSlots.size(), 2);
EXPECT_EQ(numExtendableSlots, 2);
// The input string extendable slot has a maximum of 2 slots, so the third add should fail
GraphModel::SlotPtr thirdSlot = testNode->AddExtendedSlot(TEST_STRING_INPUT_ID);
extendableSlots = testNode->GetExtendableSlots(TEST_STRING_INPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_INPUT_ID);
EXPECT_TRUE(thirdSlot == nullptr);
EXPECT_EQ(extendableSlots.size(), 2);
EXPECT_EQ(numExtendableSlots, 2);
}
TEST_F(GraphModelIntegrationTests, RemovingExtendableSlotsBelowMinimum)
{
// Create a node with extendable slots
GraphModel::NodePtr testNode = AZStd::make_shared<ExtendableSlotsNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// The output string extendable slot has a minimum of 1 slot, so it starts with 1
auto extendableSlots = testNode->GetExtendableSlots(TEST_STRING_OUTPUT_ID);
int numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_OUTPUT_ID);
EXPECT_EQ(extendableSlots.size(), 1);
EXPECT_EQ(numExtendableSlots, 1);
// The output string extendable slot has the default maximum (100), so we can add one
GraphModel::SlotPtr firstSlot = testNode->AddExtendedSlot(TEST_STRING_OUTPUT_ID);
extendableSlots = testNode->GetExtendableSlots(TEST_STRING_OUTPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_OUTPUT_ID);
EXPECT_TRUE(firstSlot != nullptr);
EXPECT_EQ(firstSlot->GetName(), TEST_STRING_OUTPUT_ID);
EXPECT_EQ(extendableSlots.size(), 2);
EXPECT_EQ(numExtendableSlots, 2);
// The output string extednable slot has a minimum of 1, so we can remove 1
testNode->DeleteSlot(firstSlot);
extendableSlots = testNode->GetExtendableSlots(TEST_STRING_OUTPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_OUTPUT_ID);
EXPECT_EQ(extendableSlots.size(), 1);
EXPECT_EQ(numExtendableSlots, 1);
// The output string extednable slot has a minimum of 1, so attempting to remove one
// when there is only one left will fail
GraphModel::SlotPtr lastSlot = *extendableSlots.begin();
testNode->DeleteSlot(lastSlot);
extendableSlots = testNode->GetExtendableSlots(TEST_STRING_OUTPUT_ID);
numExtendableSlots = testNode->GetExtendableSlotCount(TEST_STRING_OUTPUT_ID);
EXPECT_EQ(extendableSlots.size(), 1);
EXPECT_EQ(numExtendableSlots, 1);
}
TEST_F(GraphModelIntegrationTests, CannotAddNonExtendableSlot)
{
// Create a test node
GraphModel::NodePtr testNode = AZStd::make_shared<TestNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// Can't add a non-extendable slot, so adding a non-extendable slot will fail
AZ_TEST_START_TRACE_SUPPRESSION;
GraphModel::SlotPtr newSlot = testNode->AddExtendedSlot(TEST_STRING_INPUT_ID);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_EQ(newSlot, nullptr);
}
TEST_F(GraphModelIntegrationTests, CannotDeleteNonExtendableSlot)
{
// Create a test node
GraphModel::NodePtr testNode = AZStd::make_shared<TestNode>(m_graph, m_graphContext);
EXPECT_TRUE(testNode != nullptr);
// Add our test node to the scene
AZ::Vector2 offset;
GraphCanvas::NodeId nodeId;
GraphModelIntegration::GraphControllerRequestBus::EventResult(nodeId, m_sceneId, &GraphModelIntegration::GraphControllerRequests::AddNode, testNode, offset);
// Can't delete a non-extendable slot, so deleting a non-extendable slot will fail
auto beforeSlots = testNode->GetSlots();
GraphModel::SlotPtr inputSlot = testNode->GetSlot(TEST_STRING_INPUT_ID);
testNode->DeleteSlot(inputSlot);
auto afterSlots = testNode->GetSlots();
EXPECT_EQ(beforeSlots.size(), afterSlots.size());
}
}