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/EMotionFX/Code/Tests/MCore/CommandManagerTests.cpp

267 lines
11 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
*
*/
#include <AzCore/std/optional.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzTest/AzTest.h>
#include <MCore/Source/Command.h>
#include <MCore/Source/CommandLine.h>
#include <MCore/Source/MCoreCommandManager.h>
#include <MCore/Source/CommandManagerCallback.h>
#include <Tests/MCoreSystemFixture.h>
#include <Tests/Mocks/Command.h>
#include <Tests/Mocks/CommandManagerCallback.h>
namespace EMotionFX
{
class TestCommand
: public MCore::Command
{
public:
TestCommand(const AZStd::optional<float>& newValue, float* data = nullptr, MCore::Command* orgCommand = nullptr)
: MCore::Command(TestCommand::s_commandName, orgCommand),
m_data(data),
m_value(newValue)
{
}
bool Execute([[maybe_unused]] const MCore::CommandLine& parameters, [[maybe_unused]] AZStd::string& outResult) override
{
ExecuteParameter<float>(m_oldValue, m_value, *m_data);
return true;
}
bool Undo([[maybe_unused]] const MCore::CommandLine& parameters, [[maybe_unused]] AZStd::string& outResult) override
{
if (m_value.has_value())
{
*m_data = m_oldValue.value();
}
return true;
}
void InitSyntax() override
{
MCore::CommandSyntax& syntax = GetSyntax();
syntax.AddParameter("value", "Test value.", MCore::CommandSyntax::PARAMTYPE_FLOAT, "0.0");
}
bool SetCommandParameters(const MCore::CommandLine& parameters) override
{
if (parameters.CheckIfHasParameter("value"))
{
m_value = parameters.GetValueAsFloat("value", this);
}
return true;
}
bool GetIsUndoable() const override { return true; }
const char* GetHistoryName() const override { return "Test Command"; }
const char* GetDescription() const override { return "Unit test command"; }
MCore::Command* Create() override { return new TestCommand(m_value, m_data, this); }
public:
static const char* s_commandName;
private:
float* m_data;
AZStd::optional<float> m_value;
AZStd::optional<float> m_oldValue;
};
const char* TestCommand::s_commandName = "TestCommand";
enum TestCommandExecutionMethod
{
Execute,
Undo,
Redo
};
class CommandGroupTester
{
public:
CommandGroupTester()
{
m_commandManager.RegisterCommand(new TestCommand(AZStd::nullopt, &m_value));
m_managerCallbackMock = new MCore::CommandManagerCallbackMock();
m_commandManager.RegisterCallback(m_managerCallbackMock);
m_callbackMock = new MCore::CommandCallbackMock(/*executePreUndo=*/true);
m_commandManager.RegisterCommandCallback(TestCommand::s_commandName, m_callbackMock);
}
void TestCommandGroup(MCore::CommandGroup& commandGroup, int numCommandsInGroupVerification, float valueBefore, float valueAfter, TestCommandExecutionMethod commandExecutionTestMethod)
{
EXPECT_EQ(commandGroup.GetNumCommands(), numCommandsInGroupVerification);
const int numCommandsInGroup = static_cast<int>(commandGroup.GetNumCommands());
AZStd::string result;
SetManagerCallbackExpectations(m_managerCallbackMock, numCommandsInGroup, commandExecutionTestMethod);
SetCommandCallbackExpectations(m_callbackMock, numCommandsInGroup, commandExecutionTestMethod);
// 1. Execute
EXPECT_TRUE(m_commandManager.ExecuteCommandGroup(commandGroup, result));
EXPECT_FLOAT_EQ(m_value, valueAfter);
// 2. Undo
if (commandExecutionTestMethod == TestCommandExecutionMethod::Undo ||
commandExecutionTestMethod == TestCommandExecutionMethod::Redo)
{
EXPECT_TRUE(m_commandManager.Undo(result));
EXPECT_FLOAT_EQ(m_value, valueBefore);
}
// 3. Redo
if (commandExecutionTestMethod == TestCommandExecutionMethod::Redo)
{
EXPECT_TRUE(m_commandManager.Redo(result));
EXPECT_FLOAT_EQ(m_value, valueAfter);
}
}
static void SetManagerCallbackExpectations(MCore::CommandManagerCallbackMock* managerCallback, int numCommandsInGroup, TestCommandExecutionMethod executionMethod)
{
int numExecutedCommands = -1;
int numExecutedCommandGroups = -1;
int numUndoCommands = -1;
int numSetCurrentCommandCalls = -1;
switch (executionMethod)
{
case TestCommandExecutionMethod::Execute:
numExecutedCommandGroups = 1;
numExecutedCommands = numCommandsInGroup;
numUndoCommands = 0;
numSetCurrentCommandCalls = 0;
break;
case TestCommandExecutionMethod::Undo:
numExecutedCommandGroups = 2; // Undo is a parameter of the execute command group callback and thus we're expecting two calls.
numExecutedCommands = numCommandsInGroup;
numUndoCommands = numCommandsInGroup;
numSetCurrentCommandCalls = 1;
break;
case TestCommandExecutionMethod::Redo:
numExecutedCommandGroups = 3;
numExecutedCommands = 2 * numCommandsInGroup; // On redo, all commands are executed again.
numUndoCommands = numCommandsInGroup; // Now new undo calls, but we called undo before we can redo.
numSetCurrentCommandCalls = 2;
break;
default:
EXPECT_TRUE(false) << "Invalid execution method passed.";
}
EXPECT_CALL(*managerCallback, OnPreExecuteCommandGroup(::testing::_, ::testing::_)).Times(numExecutedCommandGroups);
EXPECT_CALL(*managerCallback, OnPostExecuteCommandGroup(::testing::_, ::testing::_)).Times(numExecutedCommandGroups);
EXPECT_CALL(*managerCallback, OnPreExecuteCommand(::testing::_, ::testing::_, ::testing::_)).Times(numExecutedCommands);
EXPECT_CALL(*managerCallback, OnPostExecuteCommand(::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)).Times(numExecutedCommands);
EXPECT_CALL(*managerCallback, OnPreUndoCommand(::testing::_, ::testing::_)).Times(numUndoCommands);
EXPECT_CALL(*managerCallback, OnPostUndoCommand(::testing::_, ::testing::_)).Times(numUndoCommands);
// We are adding only one item to the history with an execute call, the command group itself.
const int numNewHistoryItems = 1;
EXPECT_CALL(*managerCallback, OnAddCommandToHistory(::testing::_, ::testing::_, ::testing::_, ::testing::_))
.Times(numNewHistoryItems);
// Set current command gets called for undo as well as redo operations.
EXPECT_CALL(*managerCallback, OnSetCurrentCommand(::testing::_))
.Times(numSetCurrentCommandCalls);
}
static void SetCommandCallbackExpectations(MCore::CommandCallbackMock* callback, int numTimes, TestCommandExecutionMethod executionMethod)
{
const int numExecuteCalls = (executionMethod == TestCommandExecutionMethod::Redo) ? 2 * numTimes : numTimes;
EXPECT_CALL(*callback, Execute(::testing::_, ::testing::_))
.Times(numExecuteCalls)
.WillRepeatedly(::testing::Return(true));
if (executionMethod == TestCommandExecutionMethod::Execute)
{
EXPECT_CALL(*callback, Undo(::testing::_, ::testing::_))
.Times(0);
}
else
{
EXPECT_CALL(*callback, Undo(::testing::_, ::testing::_))
.Times(numTimes)
.WillRepeatedly(::testing::Return(true));
}
}
public:
MCore::CommandManager m_commandManager;
MCore::CommandManagerCallbackMock* m_managerCallbackMock = nullptr;
MCore::CommandCallbackMock* m_callbackMock = nullptr;
float m_value = 0.0f;
};
class CommandGroupFixture
: public MCoreSystemFixture
, public ::testing::WithParamInterface<TestCommandExecutionMethod>
{
};
TEST_P(CommandGroupFixture, StringBasedCommandGroup_OneCommand)
{
MCore::CommandGroup commandGroup;
commandGroup.AddCommandString("TestCommand -value 1.0");
CommandGroupTester tester;
tester.TestCommandGroup(commandGroup, 1, 0.0f, 1.0f, GetParam());
}
TEST_P(CommandGroupFixture, StringBasedCommandGroup_MultipleCommands)
{
MCore::CommandGroup commandGroup;
commandGroup.AddCommandString("TestCommand -value 1.0");
commandGroup.AddCommandString("TestCommand -value 2.0");
commandGroup.AddCommandString("TestCommand -value 3.0");
CommandGroupTester tester;
tester.TestCommandGroup(commandGroup, 3, 0.0f, 3.0f, GetParam());
}
TEST_P(CommandGroupFixture, ObjectBasedCommandGroup_OneCommand)
{
CommandGroupTester tester;
MCore::CommandGroup commandGroup;
commandGroup.AddCommand(new TestCommand(1.0f, &tester.m_value));
tester.TestCommandGroup(commandGroup, 1, 0.0f, 1.0f, GetParam());
}
TEST_P(CommandGroupFixture, ObjectBasedCommandGroup_MultipleCommands)
{
CommandGroupTester tester;
MCore::CommandGroup commandGroup;
commandGroup.AddCommand(new TestCommand(1.0f, &tester.m_value));
commandGroup.AddCommand(new TestCommand(2.0f, &tester.m_value));
commandGroup.AddCommand(new TestCommand(3.0f, &tester.m_value));
tester.TestCommandGroup(commandGroup, 3, 0.0f, 3.0f, GetParam());
}
TEST_P(CommandGroupFixture, ObjectBasedCommandGroup_Mixed)
{
CommandGroupTester tester;
MCore::CommandGroup commandGroup;
commandGroup.AddCommandString("TestCommand -value 1.0");
commandGroup.AddCommand(new TestCommand(2.0f, &tester.m_value));
commandGroup.AddCommandString("TestCommand -value 3.0");
commandGroup.AddCommand(new TestCommand(4.0f, &tester.m_value));
tester.TestCommandGroup(commandGroup, 4, 0.0f, 4.0f, GetParam());
}
INSTANTIATE_TEST_CASE_P(CommandGroupTests,
CommandGroupFixture,
::testing::ValuesIn({ TestCommandExecutionMethod::Execute, TestCommandExecutionMethod::Undo, TestCommandExecutionMethod::Redo}));
} // namespace EMotionFX