emberemu/tests/Commands.cpp

401 lines
11 KiB
C++
Raw Permalink Normal View History

2026-02-21 01:40:19 +00:00
/*
* Copyright (c) 2026 Ember
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <commands/Commands.h>
2026-02-21 01:40:19 +00:00
#include <gtest/gtest.h>
#include <array>
#include <ranges>
using namespace ember;
class Registry : public ::testing::Test {
public:
virtual void SetUp() {
registry = commands::create("root");
auto cmd = registry->insert("root")
2026-02-21 01:40:19 +00:00
->description("root command")
->argument<std::string>("arg1")
->argument<std::string>("arg2")
->argument<std::uint32_t>("arg3", commands::optional)
2026-02-21 01:40:19 +00:00
->handler([&](auto /*args*/) {
success = true;
});
cmd->insert("root_nested_1");
cmd->insert("root_nested_2")
->insert("root_nested_2_1");
registry->insert("root_1"); // error: unavailable
registry->insert("root_2")->insert("root_2_1"); // error: subcommands only
2026-02-21 01:40:19 +00:00
}
virtual void TearDown() {}
std::shared_ptr<commands::Command> registry;
2026-02-21 01:40:19 +00:00
bool success = false;
};
using Commands = Registry;
TEST_F(Registry, ParseInput) {
const auto input = "Lorem ipsum dolor sit amet";
const auto tokens = commands::parse_input(input);
2026-02-21 01:40:19 +00:00
const std::array<std::string_view, 5> expected {
"Lorem", "ipsum", "dolor", "sit", "amet"
};
ASSERT_EQ(tokens.size(), expected.size());
for(auto [t, e] : std::views::zip(tokens, expected)) {
ASSERT_EQ(t, e);
}
}
TEST_F(Registry, ParseQuotedInput) {
const auto input = R"(Lorem "ipsum dolor" sit amet)";
const auto tokens = commands::parse_input(input);
2026-02-21 01:40:19 +00:00
const std::array<std::string_view, 4> expected {
"Lorem", "ipsum dolor", "sit", "amet"
};
ASSERT_EQ(tokens.size(), expected.size());
for(auto [t, e] : std::views::zip(tokens, expected)) {
ASSERT_EQ(t, e);
}
}
TEST_F(Registry, ParseEscapeQuotedInput) {
const auto input = R"("Hello, "\"world!"\")";
const auto tokens = commands::parse_input(input);
2026-02-21 01:40:19 +00:00
const std::array<std::string_view, 1> expected {
R"(Hello, "world!")"
};
ASSERT_EQ(tokens.size(), expected.size());
for(auto [t, e] : std::views::zip(tokens, expected)) {
ASSERT_EQ(t, e);
}
}
TEST_F(Registry, ParseBadEscapedInput) {
const std::string_view input = R"("Hello, \world")";
ASSERT_THROW(commands::parse_input(input), commands::parse_error);
2026-02-21 01:40:19 +00:00
}
TEST_F(Registry, AddCommand) {
registry->insert("test")
2026-02-21 01:40:19 +00:00
->description("test_description");
const auto input = commands::parse_input("test");
ASSERT_NE(registry->find(input).command, nullptr);
2026-02-21 01:40:19 +00:00
}
TEST_F(Registry, EraseCommand) {
ASSERT_TRUE(registry->erase("root"));
ASSERT_EQ(registry->find("root").command, nullptr);
2026-02-21 01:40:19 +00:00
}
TEST_F(Registry, EraseNotFoundCommand) {
ASSERT_FALSE(registry->erase("fake_command"));
2026-02-21 01:40:19 +00:00
}
TEST_F(Registry, SearchSubcommands) {
const auto result = registry->find("root root_nested_1 root_nested_2");
2026-02-21 01:40:19 +00:00
ASSERT_TRUE(result.command);
ASSERT_EQ(result.depth, 2);
}
TEST_F(Registry, Autocomplete) {
const auto result = registry->autocomplete("r");
2026-02-21 01:40:19 +00:00
ASSERT_EQ(result.substring, "root ");
ASSERT_EQ(result.records.size(), 3);
ASSERT_EQ(result.records.front().name, "root");
ASSERT_EQ(result.records.front().desc, "root command");
}
TEST_F(Registry, AutocompleteSubcommands) {
2026-04-01 23:01:00 +01:00
const auto result = registry->autocomplete("root r");
2026-02-21 01:40:19 +00:00
ASSERT_EQ(result.substring, "root root_nested_");
ASSERT_EQ(result.records.size(), 2);
ASSERT_EQ(result.records.front().name, "root_nested_1");
ASSERT_EQ(result.records.back().name, "root_nested_2");
}
TEST_F(Registry, AutocompleteNestedSubcommand) {
2026-04-01 23:01:00 +01:00
const auto result = registry->autocomplete("root root_nested_2 r");
2026-02-21 01:40:19 +00:00
ASSERT_EQ(result.substring, "root root_nested_2 root_nested_2_1");
ASSERT_EQ(result.records.size(), 1);
ASSERT_EQ(result.records.front().name, "root_nested_2_1");
}
TEST_F(Registry, SearchNoMatch) {
const auto result = registry->autocomplete("foo");
2026-02-21 01:40:19 +00:00
ASSERT_TRUE(result.substring.empty());
ASSERT_TRUE(result.records.empty());
}
TEST_F(Registry, PathFragment) {
const auto tokens = commands::parse_input("root root_nested_2 root_nested_2_1");
2026-02-21 01:40:19 +00:00
ASSERT_EQ(commands::path_fragment(tokens, 0), "");
ASSERT_EQ(commands::path_fragment(tokens, 1), "root");
ASSERT_EQ(commands::path_fragment(tokens, 2), "root root_nested_2");
ASSERT_EQ(commands::path_fragment(tokens, 3), "root root_nested_2 root_nested_2_1");
ASSERT_EQ(commands::path_fragment(tokens, 4), "");
}
TEST_F(Commands, InsertCommand) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
cmd->insert("subcommand")->description("It's a subcommand");
auto subcommands = cmd->commands();
ASSERT_EQ(subcommands.size(), 3);
ASSERT_EQ(subcommands.contains("subcommand"), 1);
ASSERT_EQ(subcommands["subcommand"]->description(), "It's a subcommand");
}
TEST_F(Commands, EraseCommand) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
cmd->erase("root_nested_1");
ASSERT_FALSE(cmd->commands().contains("root_nested_1"));
}
TEST_F(Commands, ClearCommands) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
cmd->clear_commands();
ASSERT_TRUE(cmd->commands().empty());
}
TEST_F(Commands, UpdateDescription) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
ASSERT_EQ(cmd->description(), "root command");
cmd->description("New description!");
ASSERT_EQ(cmd->description(), "New description!");
}
TEST_F(Commands, ArgumentCount) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
ASSERT_EQ(cmd->argument_count(), 3);
}
TEST_F(Commands, AddArgument) {
auto cmd = registry->find("root").command;
cmd->argument<char>("arg4", commands::optional);
2026-02-21 01:40:19 +00:00
ASSERT_EQ(cmd->argument_count(), 4);
}
TEST_F(Commands, AddArgumentOutOfOrder) {
auto cmd = registry->find("root").command;
ASSERT_THROW(cmd->argument<char>("arg4"), std::invalid_argument);
2026-02-21 01:40:19 +00:00
}
TEST_F(Commands, EraseInsertArgument) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
ASSERT_TRUE(cmd->erase_argument("arg3"));
cmd->argument<char>("arg3");
2026-02-21 01:40:19 +00:00
ASSERT_EQ(cmd->argument_count(), 3);
}
TEST_F(Commands, EraseArgument) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
ASSERT_TRUE(cmd->erase_argument("arg1"));
}
TEST_F(Commands, EraseNotFoundArgument) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
ASSERT_FALSE(cmd->erase_argument("fake_arg1"));
}
TEST_F(Commands, ClearArguments) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
cmd->clear_arguments();
ASSERT_TRUE(cmd->arguments().empty());
}
TEST_F(Commands, ExecuteNoHandler) {
auto cmd = registry->find("root_1").command;
2026-02-21 01:40:19 +00:00
ASSERT_EQ(cmd->execute(), commands::Result::unavailable);
}
TEST_F(Commands, Execute_InvalidTypes) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
std::array<std::any, 3> args {
2026-02-21 01:40:19 +00:00
"Are you sure that's the point, Doctor?",
"Of course. What else could it be?",
"That you should never tell the same lie twice."
};
ASSERT_EQ(cmd->execute(args), commands::Result::invalid_types);
}
TEST_F(Commands, Execute_SubcommandsOnly) {
auto cmd = registry->find("root_2").command;
2026-02-21 01:40:19 +00:00
ASSERT_EQ(cmd->execute(), commands::Result::subcommands);
}
TEST_F(Commands, Execute_MissingArgsEmpty) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
ASSERT_EQ(cmd->execute(), commands::Result::missing_args);
}
TEST_F(Commands, Execute_TooManyArgs) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
std::array<std::any, 4> args {
2026-02-21 01:40:19 +00:00
"The", "quick", "brown", "fox"
};
ASSERT_EQ(cmd->execute(args), commands::Result::too_many_args);
}
TEST_F(Commands, Execute_MissingArgs) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
std::array<std::any, 1> args {
2026-02-21 01:40:19 +00:00
"jumped",
};
ASSERT_EQ(cmd->execute(), commands::Result::missing_args);
}
TEST_F(Commands, Execute_RequiredArgs_Success) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
std::array<std::any, 2> args {
std::string("over"), std::string("the"),
2026-02-21 01:40:19 +00:00
};
ASSERT_EQ(cmd->execute(args), commands::Result::success);
ASSERT_TRUE(success);
}
TEST_F(Commands, Execute_OptionalArgs_Success) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
std::array<std::any, 3> args {
std::string("lazy"), std::string("dog"), std::uint32_t(42)
2026-02-21 01:40:19 +00:00
};
ASSERT_EQ(cmd->execute(args), commands::Result::success);
ASSERT_TRUE(success);
}
TEST_F(Commands, Execute_AllArgTypes_Success) {
auto cmd = registry->find("root").command;
2026-02-21 01:40:19 +00:00
cmd->clear_arguments();
struct UserData {
int foo;
float bar;
} data{};
cmd->argument<std::string>("arg1")
->argument<char>("arg2")
->argument<double>("arg3")
->argument<float>("arg4")
->argument<std::int16_t>("arg5")
->argument<std::int32_t>("arg6")
->argument<std::int64_t>("arg7")
->argument<std::int8_t>("arg8")
->argument<std::uint16_t>("arg9")
->argument<std::uint32_t>("arg10")
->argument<std::uint64_t>("arg11")
->argument<std::uint8_t>("arg12")
->argument<UserData>("arg13");
std::array<std::any, 13> args {
std::string("Hello, world"), 'c', 1.0, 1.0f,
2026-02-21 01:40:19 +00:00
std::int16_t(0), std::int32_t(0), std::int64_t(0), std::int8_t(0),
std::uint16_t(0), std::uint32_t(0), std::uint64_t(0), std::uint8_t(0),
data
2026-02-21 01:40:19 +00:00
};
ASSERT_EQ(cmd->execute(args), commands::Result::success);
ASSERT_TRUE(success);
}
2026-02-27 01:45:01 +00:00
TEST_F(Commands, CommandFlags) {
auto command = registry->insert("test")
2026-02-27 01:45:01 +00:00
->flags({ 42, 117 });
ASSERT_EQ(command->flags().custom, 42);
ASSERT_EQ(command->flags().security, 117);
}
TEST_F(Commands, CommandFlagsUpdate) {
auto command = registry->insert("test")
2026-02-27 01:45:01 +00:00
->flags({ 42, 117 });
command->flags({ 0xB105F00D, 0x8BADF00D });
ASSERT_EQ(command->flags().custom, 0xB105F00D);
ASSERT_EQ(command->flags().security, 0x8BADF00D);
2026-04-07 10:03:56 +01:00
}
2026-04-07 20:36:39 +01:00
TEST_F(Commands, ScopedCommandInsertReset) {
auto command = registry->scoped_insert("scoped_test");
auto result = registry->find("scoped_test");
ASSERT_TRUE(result.command);
command.reset();
result = registry->find("scoped_test");
ASSERT_FALSE(result.command);
}
TEST_F(Commands, ScopedSubcommandInsertReset) {
auto command = registry->scoped_insert("scoped_test");
command->insert("scoped_subcommand");
auto result = registry->find("scoped_test scoped_subcommand");
ASSERT_TRUE(result.command);
command.reset();
result = registry->find("scoped_test scoped_subcommand");
ASSERT_FALSE(result.command);
}
TEST_F(Commands, ScopedCommandPtrInsertReset) {
auto root = commands::create("foo");
root->insert("bar");
auto scoped = registry->scoped_insert(root);
auto result = registry->find("foo bar");
ASSERT_TRUE(result.command);
scoped.reset();
result = registry->find("foo bar");
ASSERT_FALSE(result.command);
}
TEST_F(Commands, ScopedCommandRAIIScope) {
auto root = commands::create("foo");
root->insert("bar");
{
auto scoped = registry->scoped_insert(root);
auto result = registry->find("foo bar");
ASSERT_TRUE(result.command);
}
auto result = registry->find("foo bar");
ASSERT_FALSE(result.command);
}
TEST_F(Commands, ScopedCommandRelease) {
auto root = commands::create("foo");
root->insert("bar");
{
auto scoped = registry->scoped_insert(root);
auto result = registry->find("foo bar");
ASSERT_TRUE(result.command);
scoped.release(); // not a leak, just downgrades to non-scoped
}
auto result = registry->find("foo bar");
ASSERT_TRUE(result.command);
2026-02-21 01:40:19 +00:00
}