testing: implementing tests for configuration parsing (#56)

This commit is contained in:
Matthew Kosarek 2024-03-10 16:40:26 -04:00 committed by GitHub
parent f111fda16a
commit 001278c6e7
6 changed files with 465 additions and 68 deletions

View File

@ -24,8 +24,7 @@ pkg_check_modules(LIBNOTIFY REQUIRED IMPORTED_TARGET libnotify)
include(GNUInstallDirs)
add_executable(miracle-wm
src/main.cpp
add_library(miracle-wm-implementation
src/policy.cpp
src/tree.cpp
src/node.cpp
@ -42,10 +41,18 @@ add_executable(miracle-wm
src/workspace_content.cpp
)
target_include_directories(miracle-wm PUBLIC SYSTEM ${MIRAL_INCLUDE_DIRS})
target_link_libraries( miracle-wm ${MIRAL_LDFLAGS}
add_executable(miracle-wm
src/main.cpp
)
target_include_directories(miracle-wm-implementation PUBLIC SYSTEM ${MIRAL_INCLUDE_DIRS})
target_link_libraries( miracle-wm-implementation ${MIRAL_LDFLAGS}
PkgConfig::YAML PkgConfig::GLIB PkgConfig::LIBEVDEV PkgConfig::LIBNOTIFY nlohmann_json::nlohmann_json)
target_link_libraries(miracle-wm miracle-wm-implementation)
target_include_directories(miracle-wm PUBLIC SYSTEM ${MIRAL_INCLUDE_DIRS})
target_link_libraries(miracle-wm ${MIRAL_LDFLAGS})
install(PROGRAMS ${CMAKE_BINARY_DIR}/bin/miracle-wm
DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@ -40,9 +40,21 @@ MiracleConfig::MiracleConfig(miral::MirRunner& runner)
_watch(runner);
}
MiracleConfig::MiracleConfig(miral::MirRunner& runner, std::string const& path)
: runner{runner},
config_path{path}
{
{
std::fstream file(config_path, std::ios::out | std::ios::in | std::ios::app);
}
mir::log_info("Configuration file path is: %s", config_path.c_str());
_load();
_watch(runner);
}
void MiracleConfig::_load()
{
std::lock_guard<std::mutex> lock(mutex);
// Reset all
@ -106,7 +118,23 @@ void MiracleConfig::_load()
continue;
}
auto name = sub_node["name"].as<std::string>();
std::string name;
std::string action;
std::string key;
YAML::Node modifiers_node;
try
{
name = sub_node["name"].as<std::string>();
action = sub_node["action"].as<std::string>();
key = sub_node["key"].as<std::string>();
modifiers_node = sub_node["modifiers"];
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse default_action_override[%d]: %s", i, e.msg.c_str());
continue;
}
DefaultKeyCommand key_command;
if (name == "terminal")
key_command = DefaultKeyCommand::Terminal;
@ -187,7 +215,6 @@ void MiracleConfig::_load()
continue;
}
auto action = sub_node["action"].as<std::string>();
MirKeyboardAction keyboard_action;
if (action == "up")
keyboard_action = MirKeyboardAction::mir_keyboard_action_up;
@ -202,7 +229,6 @@ void MiracleConfig::_load()
continue;
}
auto key = sub_node["key"].as<std::string>();
auto code = libevdev_event_code_from_name(EV_KEY, key.c_str()); //https://stackoverflow.com/questions/32059363/is-there-a-way-to-get-the-evdev-keycode-from-a-string
if (code < 0)
{
@ -210,8 +236,6 @@ void MiracleConfig::_load()
continue;
}
auto modifiers_node = sub_node["modifiers"];
if (!modifiers_node.IsSequence())
{
mir::log_error("default_action_overrides: Provided modifiers is not an array");
@ -219,12 +243,25 @@ void MiracleConfig::_load()
}
uint modifiers = 0;
bool is_invalid = false;
for (auto j = 0; j < modifiers_node.size(); j++)
{
auto modifier = modifiers_node[j].as<std::string>();
modifiers = modifiers | parse_modifier(modifier);
try
{
auto modifier = modifiers_node[j].as<std::string>();
modifiers = modifiers | parse_modifier(modifier);
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse modifier for default_action_overrides[%d]: %s", i, e.msg.c_str());
is_invalid = true;
break;
}
}
if (is_invalid)
continue;
key_commands[key_command].push_back({
keyboard_action,
modifiers,
@ -465,9 +502,24 @@ void MiracleConfig::_load()
continue;
}
std::string command;
std::string action;
std::string key;
YAML::Node modifiers_node;
try
{
command = sub_node["command"].as<std::string>();
action = sub_node["action"].as<std::string>();
key = sub_node["key"].as<std::string>();
modifiers_node = sub_node["modifiers"];
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse custom_actions[%d]: %s", i, e.msg.c_str());
continue;
}
// TODO: Copy & paste here
auto command = sub_node["command"].as<std::string>();
auto action = sub_node["action"].as<std::string>();
MirKeyboardAction keyboard_action;
if (action == "up")
keyboard_action = MirKeyboardAction::mir_keyboard_action_up;
@ -482,7 +534,6 @@ void MiracleConfig::_load()
continue;
}
auto key = sub_node["key"].as<std::string>();
auto code = libevdev_event_code_from_name(EV_KEY,
key.c_str()); //https://stackoverflow.com/questions/32059363/is-there-a-way-to-get-the-evdev-keycode-from-a-string
if (code < 0)
@ -493,8 +544,6 @@ void MiracleConfig::_load()
continue;
}
auto modifiers_node = sub_node["modifiers"];
if (!modifiers_node.IsSequence())
{
mir::log_error("custom_actions: Provided modifiers is not an array");
@ -502,12 +551,25 @@ void MiracleConfig::_load()
}
uint modifiers = 0;
bool is_invalid = false;
for (auto j = 0; j < modifiers_node.size(); j++)
{
auto modifier = modifiers_node[j].as<std::string>();
modifiers = modifiers | parse_modifier(modifier);
try
{
auto modifier = modifiers_node[j].as<std::string>();
modifiers = modifiers | parse_modifier(modifier);
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse modifier for custom_actions[%d]: %s", i, e.msg.c_str());
is_invalid = true;
break;
}
}
if (is_invalid)
continue;
custom_key_commands.push_back({
keyboard_action,
modifiers,
@ -520,45 +582,90 @@ void MiracleConfig::_load()
// Gap sizes
if (config["inner_gaps"])
{
if (config["inner_gaps"]["x"])
inner_gaps_x = config["inner_gaps"]["x"].as<int>();
if (config["inner_gaps"]["y"])
inner_gaps_y = config["inner_gaps"]["y"].as<int>();
int new_inner_gaps_x = inner_gaps_x;
int new_inner_gaps_y = inner_gaps_y;
try
{
if (config["inner_gaps"]["x"])
new_inner_gaps_x = config["inner_gaps"]["x"].as<int>();
if (config["inner_gaps"]["y"])
new_inner_gaps_y = config["inner_gaps"]["y"].as<int>();
inner_gaps_x = new_inner_gaps_x;
inner_gaps_y = new_inner_gaps_y;
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse inner_gaps: %s", e.msg.c_str());
}
}
if (config["outer_gaps"])
{
if (config["outer_gaps"]["x"])
outer_gaps_x = config["outer_gaps"]["x"].as<int>();
if (config["outer_gaps"]["y"])
outer_gaps_y = config["outer_gaps"]["y"].as<int>();
try
{
int new_outer_gaps_x = outer_gaps_x;
int new_outer_gaps_y = outer_gaps_y;
if (config["outer_gaps"]["x"])
new_outer_gaps_x = config["outer_gaps"]["x"].as<int>();
if (config["outer_gaps"]["y"])
new_outer_gaps_y = config["outer_gaps"]["y"].as<int>();
outer_gaps_x = new_outer_gaps_x;
outer_gaps_y = new_outer_gaps_y;
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse outer_gaps: %s", e.msg.c_str());
}
}
// Startup Apps
if (config["startup_apps"])
{
for (auto const& node : config["startup_apps"])
if (!config["startup_apps"].IsSequence())
{
if (!node["command"])
mir::log_error("startup_apps is not an array");
}
else
{
for (auto const& node : config["startup_apps"])
{
mir::log_error("startup_apps: app lacks a command");
continue;
}
if (!node["command"])
{
mir::log_error("startup_apps: app lacks a command");
continue;
}
auto command = node["command"].as<std::string>();
bool restart_on_death = false;
if (node["restart_on_death"])
{
restart_on_death = node["restart_on_death"].as<bool>();
}
try
{
auto command = node["command"].as<std::string>();
bool restart_on_death = false;
if (node["restart_on_death"])
{
restart_on_death = node["restart_on_death"].as<bool>();
}
startup_apps.push_back({std::move(command), restart_on_death});
startup_apps.push_back({std::move(command), restart_on_death});
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse startup_apps: %s", e.msg.c_str());
}
}
}
}
// Terminal
if (config["terminal"])
{
terminal = config["terminal"].as<std::string>();
try
{
terminal = config["terminal"].as<std::string>();
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse terminal: %s", e.msg.c_str());
}
}
if (terminal && !program_exists(terminal.value()))
@ -570,7 +677,14 @@ void MiracleConfig::_load()
// Resizing
if (config["resize_jump"])
{
resize_jump = config["resize_jump"].as<int>();
try
{
resize_jump = config["resize_jump"].as<int>();
}
catch (YAML::BadConversion const& e)
{
mir::log_error("Unable to parse resize_jump: %s", e.msg.c_str());
}
}
}

View File

@ -86,6 +86,7 @@ class MiracleConfig
{
public:
MiracleConfig(miral::MirRunner&);
MiracleConfig(miral::MirRunner&, std::string const&);
[[nodiscard]] MirInputEventModifier get_input_event_modifier() const;
CustomKeyCommand const* matches_custom_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const;
[[nodiscard]] DefaultKeyCommand matches_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const;

View File

@ -12,8 +12,10 @@ include_directories(
find_package(PkgConfig)
pkg_check_modules(MIRAL miral REQUIRED)
find_package(GTest REQUIRED)
add_executable(miracle-wm-tests window_tree_test.cpp)
pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-cpp)
add_executable(miracle-wm-tests miracle_config_test.cpp)
target_include_directories(miracle-wm-tests PUBLIC SYSTEM ${GTEST_INCLUDE_DIRS} ${MIRAL_INCLUDE_DIRS})
target_link_libraries(miracle-wm-tests GTest::gtest_main ${GTEST_LIBRARIES} ${MIRAL_LDFLAGS} pthread)
target_link_libraries(miracle-wm-tests GTest::gtest_main miracle-wm-implementation ${GTEST_LIBRARIES} ${MIRAL_LDFLAGS} PkgConfig::YAML pthread)
gtest_discover_tests(miracle-wm-tests)

View File

@ -0,0 +1,298 @@
#include <gtest/gtest.h>
#include "miracle_config.h"
#include <miral/runner.h>
#include <filesystem>
#include <fstream>
#include <vector>
#include "yaml-cpp/yaml.h"
using namespace miracle;
namespace
{
int argc = 1;
char const* argv[] = {"miracle-wm-tests"};
const std::string path = std::filesystem::current_path() / "test.yaml";
}
class MiracleConfigTest : public testing::Test
{
public:
MiracleConfigTest() : runner(argc, argv)
{
}
void SetUp() override
{
std::ofstream ofs;
ofs.open(path, std::ofstream::out | std::ofstream::trunc);
ofs.close();
}
void TearDown() override
{
std::filesystem::remove(path.c_str());
}
void write_kvp(std::string key, std::string value)
{
std::fstream file(path, std::ios::app);
file << key << ": " << value << "\n";
}
void write_yaml_node(YAML::Node const& node)
{
std::fstream file(path, std::ios::app);
file << node;
}
miral::MirRunner runner;
};
TEST_F(MiracleConfigTest, ConfigurationLoadingDoesNotFailWhenFileDoesNotExist)
{
std::filesystem::remove(path.c_str());
EXPECT_NO_THROW(MiracleConfig config(runner, path));
}
TEST_F(MiracleConfigTest, ConfigurationLoadingDoesNotFailWhenFileDoesNotContainYaml)
{
std::fstream file(path, std::ios::app);
file << "Hello my name is Matthew { \"fifteen\": 15 } Goodbye then!";
EXPECT_NO_THROW(MiracleConfig config(runner, path));
}
TEST_F(MiracleConfigTest, DefaultModifierIsMeta)
{
MiracleConfig config(runner, path);
ASSERT_EQ(config.get_input_event_modifier(), mir_input_event_modifier_meta);
}
TEST_F(MiracleConfigTest, CanWriteDefaultModifier)
{
write_kvp("action_key", "alt");
MiracleConfig config(runner, path);
ASSERT_EQ(config.get_input_event_modifier(), mir_input_event_modifier_alt);
}
TEST_F(MiracleConfigTest, UnknownModifiersResultsInMeta)
{
write_kvp("action_key", "unknown");
MiracleConfig config(runner, path);
ASSERT_EQ(config.get_input_event_modifier(), mir_input_event_modifier_meta);
}
TEST_F(MiracleConfigTest, WhenDefaultActionOverridesIsNotArrayThenWeDoNotFail)
{
write_kvp("default_action_overrides", "hello");
EXPECT_NO_THROW(MiracleConfig config(runner, path));
}
TEST_F(MiracleConfigTest, CanOverrideDefaultAction)
{
YAML::Node node;
YAML::Node action_override_node;
action_override_node["name"] = "terminal";
action_override_node["action"] = "down";
action_override_node["modifiers"].push_back("primary");
action_override_node["key"] = "KEY_X";
node["default_action_overrides"].push_back(action_override_node);
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(
config.matches_key_command(
MirKeyboardAction::mir_keyboard_action_down,
KEY_X,
mir_input_event_modifier_meta),
Terminal);
}
TEST_F(MiracleConfigTest, WhenEntryInDefaultActionOverridesHasInvalidNameThenItIsNotAdded)
{
YAML::Node node;
YAML::Node action_override_node;
action_override_node["name"].push_back("terminal");
action_override_node["action"] = "down";
action_override_node["modifiers"].push_back("primary");
action_override_node["key"] = "KEY_X";
node["default_action_overrides"].push_back(action_override_node);
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(
config.matches_key_command(
MirKeyboardAction::mir_keyboard_action_down,
KEY_ENTER,
mir_input_event_modifier_meta),
Terminal);
}
TEST_F(MiracleConfigTest, WhenEntryInDefaultActionOverridesHasInvalidModifiersThenItIsNotAdded)
{
YAML::Node node;
YAML::Node action_override_node;
action_override_node["name"] = "terminal";
action_override_node["action"] = "down";
action_override_node["modifiers"] = "primary";
action_override_node["key"] = "KEY_X";
node["default_action_overrides"].push_back(action_override_node);
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(
config.matches_key_command(
MirKeyboardAction::mir_keyboard_action_down,
KEY_ENTER,
mir_input_event_modifier_meta),
Terminal);
}
TEST_F(MiracleConfigTest, CanCreateCustomAction)
{
YAML::Node node;
YAML::Node action_override_node;
action_override_node["command"] = "echo Hi";
action_override_node["action"] = "down";
action_override_node["modifiers"].push_back("primary");
action_override_node["key"] = "KEY_X";
node["custom_actions"].push_back(action_override_node);
write_yaml_node(node);
MiracleConfig config(runner, path);
auto custom_action = config.matches_custom_key_command(
MirKeyboardAction::mir_keyboard_action_down,
KEY_X,
mir_input_event_modifier_meta);
EXPECT_EQ(custom_action->command, "echo Hi");
EXPECT_EQ(custom_action->key, KEY_X);
EXPECT_EQ(custom_action->action, mir_keyboard_action_down);
}
TEST_F(MiracleConfigTest, CustomActionWithInvalidCommandIsNotAdded)
{
YAML::Node node;
YAML::Node action_override_node;
action_override_node["command"].push_back("echo Hi");
action_override_node["action"] = "down";
action_override_node["modifiers"].push_back("primary");
action_override_node["key"] = "KEY_X";
node["custom_actions"].push_back(action_override_node);
write_yaml_node(node);
MiracleConfig config(runner, path);
auto custom_action = config.matches_custom_key_command(
MirKeyboardAction::mir_keyboard_action_down,
KEY_X,
mir_input_event_modifier_meta);
EXPECT_EQ(custom_action, nullptr);
}
TEST_F(MiracleConfigTest, InvalidInnerGapsResolveToDefault)
{
YAML::Node node;
YAML::Node vec;
vec["x"] = "hello";
vec["y"] = "goodbye";
node["inner_gaps"] = vec;
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_inner_gaps_x(), 10);
EXPECT_EQ(config.get_inner_gaps_y(), 10);
}
TEST_F(MiracleConfigTest, ValidInnerGapsAreSetCorrectly)
{
YAML::Node node;
YAML::Node vec;
vec["x"] = 33;
vec["y"] = 44;
node["inner_gaps"] = vec;
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_inner_gaps_x(), 33);
EXPECT_EQ(config.get_inner_gaps_y(), 44);
}
TEST_F(MiracleConfigTest, InvalidOuterGapsResolveToDefault)
{
YAML::Node node;
YAML::Node vec;
vec["x"] = "hello";
vec["y"] = "goodbye";
node["outer_gaps"] = vec;
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_outer_gaps_x(), 10);
EXPECT_EQ(config.get_outer_gaps_y(), 10);
}
TEST_F(MiracleConfigTest, ValidOuterGapsAreSetCorrectly)
{
YAML::Node node;
YAML::Node vec;
vec["x"] = 33;
vec["y"] = 44;
node["outer_gaps"] = vec;
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_outer_gaps_x(), 33);
EXPECT_EQ(config.get_outer_gaps_y(), 44);
}
TEST_F(MiracleConfigTest, ValidStartupAppsAreParsed)
{
YAML::Node node;
YAML::Node startup_app;
startup_app["command"] = "echo Hi";
startup_app["restart_on_death"] = true;
node["startup_apps"].push_back(startup_app);
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_startup_apps().size(), 1);
EXPECT_EQ(config.get_startup_apps()[0].command, "echo Hi");
EXPECT_EQ(config.get_startup_apps()[0].restart_on_death, true);
}
TEST_F(MiracleConfigTest, StartupAppsThatIsNotAnArrayIsNotParsed)
{
YAML::Node node;
YAML::Node startup_app;
node["startup_apps"] = "Hello";
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_startup_apps().size(), 0);
}
TEST_F(MiracleConfigTest, StartupAppsInvalidCommandIsNotParsed)
{
YAML::Node node;
YAML::Node startup_app;
startup_app["command"].push_back("Hello");
startup_app["restart_on_death"] = false;
node["startup_apps"].push_back(startup_app);
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_startup_apps().size(), 0);
}
TEST_F(MiracleConfigTest, StartupAppsInvalidRestartOnDeathIsNotParsed)
{
YAML::Node node;
YAML::Node startup_app;
startup_app["command"] = "echo Hi";
startup_app["restart_on_death"] = "Hello";
node["startup_apps"].push_back(startup_app);
write_yaml_node(node);
MiracleConfig config(runner, path);
EXPECT_EQ(config.get_startup_apps().size(), 0);
}

View File

@ -1,25 +0,0 @@
#include <gtest/gtest.h>
#include <math.h>
double squareRoot(const double a)
{
double b = sqrt(a);
if(b != b) // NaN check
{ return -1.0; }
else
{ return sqrt(a); }
}
TEST(SquareRootTest, PositiveNos)
{
ASSERT_EQ(6, squareRoot(36.0));
ASSERT_EQ(18.0, squareRoot(324.0));
ASSERT_EQ(25.4, squareRoot(645.16));
ASSERT_EQ(0, squareRoot(0.0));
}
TEST(SquareRootTest, NegativeNos)
{
ASSERT_EQ(-1.0, squareRoot(-15.0));
ASSERT_EQ(-1.0, squareRoot(-0.2));
}