container: root: test: unit: add pluggable cases

Focuses on base pluggables, pluggable data types and functionality.
This commit is contained in:
2025-12-07 13:25:59 -08:00
parent 29574ca74d
commit 7b81970330
3 changed files with 604 additions and 16 deletions

View File

@@ -39,6 +39,7 @@ namespace dfi
//! \since docker-finance 1.0.0
namespace tests
{
// TODO(afiore): move this out of common into pure unit test file
//! \namespace dfi::tests::unit
//! \brief docker-finance unit test cases
//! \ingroup cpp_tests

View File

@@ -25,6 +25,7 @@
#include <gtest/gtest.h>
#include <string>
#include <utility>
#include "../common/type.hh"
@@ -236,6 +237,97 @@ TEST_F(InvalidArgument, move_ctor)
ASSERT_STRNE(one.what(), two.what());
}
//! \brief PluggablePath type fixture
//! \ref dfi::common::type::PluggablePath
//! \since docker-finance 1.1.0
struct PluggablePathType : public ::testing::Test,
public ::dfi::common::type::PluggablePath
{
PluggablePathType()
: ::dfi::common::type::PluggablePath(
{"/path/to/repository/pluggable", "/path/to/custom/pluggable"})
{
}
const std::string kRepo{"/path/to/repository/pluggable"};
const std::string kCustom{"/path/to/custom/pluggable"};
};
TEST_F(PluggablePathType, Accessors)
{
ASSERT_EQ(repo(), kRepo);
ASSERT_EQ(custom(), kCustom);
}
TEST_F(PluggablePathType, Mutators)
{
ASSERT_NO_THROW(repo(kRepo + "/new").custom(kCustom + "/new"));
ASSERT_EQ(repo(), kRepo + "/new");
ASSERT_EQ(custom(), kCustom + "/new");
}
//! \brief PluggableSpace type fixture
//! \ref dfi::common::type::PluggableSpace
//! \since docker-finance 1.1.0
struct PluggableSpaceType : public ::testing::Test,
public ::dfi::common::type::PluggableSpace
{
PluggableSpaceType()
: ::dfi::common::type::PluggableSpace({"outer::space", "inner::space"})
{
}
const std::string kOuter{"outer::space"};
const std::string kInner{"inner::space"};
};
TEST_F(PluggableSpaceType, Accessors)
{
ASSERT_EQ(outer(), kOuter);
ASSERT_EQ(inner(), kInner);
}
TEST_F(PluggableSpaceType, Mutators)
{
ASSERT_NO_THROW(
outer(kOuter + "::more_outer").inner(kInner + "::more_inner"));
ASSERT_EQ(outer(), kOuter + "::more_outer");
ASSERT_EQ(inner(), kInner + "::more_inner");
}
//! \brief PluggableArgs type fixture
//! \ref dfi::common::type::PluggableArgs
//! \since docker-finance 1.1.0
struct PluggableArgsType : public ::testing::Test,
public ::dfi::common::type::PluggableArgs
{
PluggableArgsType()
: ::dfi::common::type::PluggableArgs(
{"loader argument;", "unloader argument;"})
{
}
const std::string kLoad{"loader argument;"};
const std::string kUnload{"unloader argument;"};
};
TEST_F(PluggableArgsType, Accessors)
{
ASSERT_EQ(load(), kLoad);
ASSERT_EQ(unload(), kUnload);
}
TEST_F(PluggableArgsType, Mutators)
{
ASSERT_NO_THROW(load(kLoad + " another argument;")
.unload(kUnload + " another argument;"));
ASSERT_EQ(load(), kLoad + " another argument;");
ASSERT_EQ(unload(), kUnload + " another argument;");
}
} // namespace unit
} // namespace tests
} // namespace dfi

View File

@@ -37,6 +37,7 @@
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
@@ -439,6 +440,333 @@ TEST_F(CommonFreeFiles, ReloadMultiple)
ASSERT_NO_THROW(common::line("my_bar bar;"));
}
//! \brief PluggablePath fixture
//! \ref dfi::common::PluggablePath
//! \since docker-finance 1.1.0
struct PluggablePath : public ::testing::Test,
public ::dfi::tests::CommonFixture
{
PluggablePath() : m_repo(kRepoPseudo), m_custom(kCustomPseudo) {}
const std::string kParent{"a_pluggable_dir"};
const std::string kChild{"a_pluggable.file"};
const std::string kFamily{kParent + "/" + kChild};
const std::string kRepoPseudo{"repo" + std::string{"/"} + kFamily};
const std::string kCustomPseudo{"custom" + std::string{"/"} + kFamily};
static constexpr std::string_view kRepoBase{"/the/repo/path/"};
static constexpr std::string_view kCustomBase{"/the/custom/path/"};
const std::string kRepoAbsolute{std::string{kRepoBase} + kFamily};
const std::string kCustomAbsolute{std::string{kCustomBase} + kFamily};
struct Path : public ::dfi::common::PluggablePath
{
using outer = ::dfi::tests::unit::PluggablePath;
explicit Path(const std::string& path)
: ::dfi::common::PluggablePath(
path,
::dfi::common::type::PluggablePath{
{std::string{outer::kRepoBase},
std::string{outer::kCustomBase}}})
{
}
auto& operator()()
{
return this->::dfi::common::PluggablePath::operator()();
}
const auto& operator()() const
{
return this->::dfi::common::PluggablePath::operator()();
}
};
Path m_repo, m_custom;
};
TEST_F(PluggablePath, Pseudo)
{
ASSERT_EQ(m_repo.pseudo(), kRepoPseudo);
ASSERT_EQ(m_custom.pseudo(), kCustomPseudo);
}
TEST_F(PluggablePath, Absolute)
{
ASSERT_EQ(m_repo.absolute(), kRepoAbsolute);
ASSERT_EQ(m_custom.absolute(), kCustomAbsolute);
}
TEST_F(PluggablePath, Parent)
{
ASSERT_EQ(m_repo.parent(), kParent);
ASSERT_EQ(m_custom.parent(), kParent);
}
TEST_F(PluggablePath, Child)
{
ASSERT_EQ(m_repo.child(), kChild);
ASSERT_EQ(m_custom.child(), kChild);
}
TEST_F(PluggablePath, Family)
{
ASSERT_EQ(m_repo.family(), kFamily);
ASSERT_EQ(m_custom.family(), kFamily);
}
TEST_F(PluggablePath, Family_nested)
{
const std::string nested{kParent + "/a_nested_dir/" + kChild};
Path repo("repo" + std::string{"/"} + nested),
custom("custom" + std::string{"/"} + nested);
ASSERT_EQ(repo.parent(), kParent);
ASSERT_EQ(custom.parent(), kParent);
ASSERT_EQ(repo.child(), kChild);
ASSERT_EQ(custom.child(), kChild);
ASSERT_EQ(repo.family(), nested);
ASSERT_EQ(custom.family(), nested);
}
TEST_F(PluggablePath, Base)
{
ASSERT_EQ(m_repo().repo(), kRepoBase);
ASSERT_EQ(m_custom().custom(), kCustomBase);
}
TEST_F(PluggablePath, Booleans)
{
ASSERT_EQ(m_repo.is_repo(), true);
ASSERT_EQ(m_repo.is_custom(), false);
ASSERT_EQ(m_custom.is_custom(), true);
ASSERT_EQ(m_custom.is_repo(), false);
}
TEST_F(PluggablePath, Mutators)
{
const std::string kOne{std::string{kRepoBase} + std::string{"one/"}};
const std::string kTwo{std::string{kCustomBase} + std::string{"two/"}};
//
// Mutated
//
ASSERT_NO_THROW(m_repo().repo(kOne).custom(""));
ASSERT_NO_THROW(m_custom().repo("").custom(kTwo));
ASSERT_EQ(m_repo().repo(), kOne);
ASSERT_EQ(m_repo().custom(), "");
ASSERT_EQ(m_custom().custom(), kTwo);
ASSERT_EQ(m_custom().repo(), "");
const std::string kOneAbsolute{kOne + kFamily};
const std::string kTwoAbsolute{kTwo + kFamily};
ASSERT_EQ(m_repo.parse().absolute(), kOneAbsolute);
ASSERT_EQ(m_custom.parse().absolute(), kTwoAbsolute);
//
// Unchanged
//
ASSERT_EQ(m_repo.parse().pseudo(), kRepoPseudo);
ASSERT_EQ(m_repo.pseudo(), kRepoPseudo);
ASSERT_EQ(m_custom.parse().pseudo(), kCustomPseudo);
ASSERT_EQ(m_custom.pseudo(), kCustomPseudo);
ASSERT_EQ(m_repo.parse().parent(), kParent);
ASSERT_EQ(m_repo.parent(), kParent);
ASSERT_EQ(m_custom.parse().parent(), kParent);
ASSERT_EQ(m_custom.parent(), kParent);
ASSERT_EQ(m_repo.parse().child(), kChild);
ASSERT_EQ(m_repo.child(), kChild);
ASSERT_EQ(m_custom.parse().child(), kChild);
ASSERT_EQ(m_custom.child(), kChild);
ASSERT_EQ(m_repo.parse().family(), kFamily);
ASSERT_EQ(m_repo.family(), kFamily);
ASSERT_EQ(m_custom.parse().family(), kFamily);
ASSERT_EQ(m_custom.family(), kFamily);
ASSERT_EQ(m_repo.parse().is_repo(), true);
ASSERT_EQ(m_repo.is_repo(), true);
ASSERT_EQ(m_repo.parse().is_custom(), false);
ASSERT_EQ(m_repo.is_custom(), false);
ASSERT_EQ(m_custom.parse().is_custom(), true);
ASSERT_EQ(m_custom.is_custom(), true);
ASSERT_EQ(m_custom.parse().is_repo(), false);
ASSERT_EQ(m_custom.is_repo(), false);
}
//! \brief PluggableSpace fixture
//! \ref dfi::common::PluggableSpace
//! \since docker-finance 1.1.0
struct PluggableSpace : public ::testing::Test,
public ::dfi::tests::CommonFixture
{
PluggableSpace() : m_space(kOuter, kInner) {}
static constexpr std::string_view kOuter{"outer::space"};
static constexpr std::string_view kInner{"inner::space"};
struct Space : public ::dfi::common::PluggableSpace
{
using space = ::dfi::tests::unit::PluggableSpace;
explicit Space(const std::string_view outer, const std::string_view inner)
: ::dfi::common::PluggableSpace(
::dfi::common::type::PluggableSpace{
{std::string{space::kOuter}, std::string{space::kInner}}})
{
}
auto& operator()()
{
return this->::dfi::common::PluggableSpace::operator()();
}
const auto& operator()() const
{
return this->::dfi::common::PluggableSpace::operator()();
}
};
Space m_space;
};
TEST_F(PluggableSpace, Accessors)
{
ASSERT_EQ(m_space.outer(), kOuter);
ASSERT_EQ(m_space.inner(), kInner);
ASSERT_EQ(m_space().outer(), kOuter);
ASSERT_EQ(m_space().inner(), kInner);
}
TEST_F(PluggableSpace, Mutators)
{
const std::string kOne{std::string{kOuter} + std::string{"::one"}};
const std::string kTwo{std::string{kInner} + std::string{"::two"}};
ASSERT_NO_THROW(m_space().outer(kOne).inner(kTwo));
ASSERT_EQ(m_space().outer(), kOne);
ASSERT_EQ(m_space().inner(), kTwo);
ASSERT_EQ(m_space.outer(), kOne);
ASSERT_EQ(m_space.inner(), kTwo);
const std::string kThree{std::string{kOuter} + std::string{"::three"}};
const std::string kFour{std::string{kInner} + std::string{"::four"}};
ASSERT_NO_THROW(m_space().outer(kThree).inner(kFour));
ASSERT_EQ(m_space().outer(), kThree);
ASSERT_EQ(m_space().inner(), kFour);
ASSERT_EQ(m_space.outer(), kThree);
ASSERT_EQ(m_space.inner(), kFour);
}
TEST_F(PluggableSpace, Booleans)
{
ASSERT_EQ(m_space.has_outer(), true);
ASSERT_EQ(m_space.has_inner(), true);
ASSERT_NO_THROW(m_space().outer("").inner(""));
ASSERT_EQ(m_space.has_outer(), false);
ASSERT_EQ(m_space.has_inner(), false);
}
//! \brief PluggableArgs fixture
//! \ref dfi::common::PluggableArgs
//! \since docker-finance 1.1.0
struct PluggableArgs : public ::testing::Test,
public ::dfi::tests::CommonFixture
{
PluggableArgs() : m_args(kLoad, kUnload) {}
static constexpr std::string_view kLoad{"using foo = int;"};
static constexpr std::string_view kUnload{"using bar = char;"};
struct Args : public ::dfi::common::PluggableArgs
{
using args = ::dfi::tests::unit::PluggableArgs;
explicit Args(const std::string_view load, const std::string_view unload)
: ::dfi::common::PluggableArgs(
::dfi::common::type::PluggableArgs{
{std::string{args::kLoad}, std::string{args::kUnload}}})
{
}
auto& operator()()
{
return this->::dfi::common::PluggableArgs::operator()();
}
const auto& operator()() const
{
return this->::dfi::common::PluggableArgs::operator()();
}
};
Args m_args;
};
TEST_F(PluggableArgs, Accessors)
{
ASSERT_EQ(m_args.load(), kLoad);
ASSERT_EQ(m_args.unload(), kUnload);
ASSERT_EQ(m_args().load(), kLoad);
ASSERT_EQ(m_args().unload(), kUnload);
}
TEST_F(PluggableArgs, Mutators)
{
const std::string kOne{std::string{kLoad} + std::string{" foo f1;"}};
const std::string kTwo{std::string{kUnload} + std::string{" bar b1;"}};
ASSERT_NO_THROW(m_args().load(kOne).unload(kTwo));
ASSERT_EQ(m_args().load(), kOne);
ASSERT_EQ(m_args().unload(), kTwo);
ASSERT_EQ(m_args.load(), kOne);
ASSERT_EQ(m_args.unload(), kTwo);
const std::string kThree{std::string{kLoad} + std::string{" foo f2;"}};
const std::string kFour{std::string{kUnload} + std::string{" bar b2;"}};
ASSERT_NO_THROW(m_args().load(kThree).unload(kFour));
ASSERT_EQ(m_args().load(), kThree);
ASSERT_EQ(m_args().unload(), kFour);
ASSERT_EQ(m_args.load(), kThree);
ASSERT_EQ(m_args.unload(), kFour);
}
TEST_F(PluggableArgs, Booleans)
{
ASSERT_EQ(m_args.has_load(), true);
ASSERT_EQ(m_args.has_unload(), true);
ASSERT_NO_THROW(m_args().load("").unload(""));
ASSERT_EQ(m_args.has_load(), false);
ASSERT_EQ(m_args.has_unload(), false);
}
//! \brief MacroFreeFiles fixture
//! \since docker-finance 1.1.0
//! \todo Move to Macro tests
@@ -463,30 +791,197 @@ TEST_F(MacroFreeFiles, LoadSingle)
// TODO(afiore): multiple load
// TODO(afiore): unload, reload
//! \brief PluginFreeFiles fixture
//! \brief Plugin command fixture for testing auto-(un|re)loading functionality
//! \details Will test loading/unloading/reloading and argument passing to plugin
//! \ref dfi::plugin
//! \note
//! `dfi`'s example repository plugin will process:
//!
//! - load argument *after* plugin is loaded
//! - unload argument *before* plugin is unloaded
//!
//! \warning Since the custom plugin bind-mount is read-only,
//! only-the-fly custom plugins cannot be created at this time.
//!
//! \since docker-finance 1.1.0
//! \todo Move to Plugin tests
struct PluginFreeFiles : public ::testing::Test,
public ::dfi::tests::CommonFixture
struct PluginCommands : public ::testing::Test,
public ::dfi::tests::CommonFixture
{
// NOTE: custom plugin bind-mount is read-only
const std::string kValidFile{"repo/example/example.cc"};
const std::string kInvalidFile{"repo/should-not/exist.cc"};
const std::string kValidArg{"dfi::plugin::example::Example e;"};
//! \note File is processed but plugin-implementation argument will intentionally throw
const std::string kInvalidArg{
"dfi::common::throw_ex<dfi::common::type::InvalidArgument>();"};
};
TEST_F(PluginFreeFiles, LoadSingle)
TEST_F(PluginCommands, LoadUnloadSingleInvalidFile)
{
ASSERT_THROW(
::dfi::plugin::load("repo/should-not/exist.cc"), type::RuntimeError);
ASSERT_THROW(
common::line("dfi::plugin::my_plugin_namespace::example2();"),
type::RuntimeError);
ASSERT_NO_THROW(::dfi::plugin::load("repo/example.cc"));
ASSERT_NO_THROW(
common::line("dfi::plugin::my_plugin_namespace::example2();"));
ASSERT_THROW(plugin::load(kInvalidFile), type::RuntimeError);
ASSERT_THROW(common::line(kValidArg), type::RuntimeError);
ASSERT_THROW(plugin::unload(kInvalidFile), type::RuntimeError);
}
// TODO(afiore): multiple load
// TODO(afiore): unload, reload
TEST_F(PluginCommands, LoadUnloadSingleSimpleNoArg)
{
ASSERT_NO_THROW(plugin::load(kValidFile));
ASSERT_NO_THROW(common::line(kValidArg));
ASSERT_NO_THROW(plugin::unload(kValidFile));
ASSERT_THROW(common::line(kValidArg), type::RuntimeError);
}
TEST_F(PluginCommands, LoadUnloadSingleSimpleWithArg)
{
ASSERT_NO_THROW(plugin::load(kValidFile, kValidArg + " e.example1();"));
ASSERT_NO_THROW(plugin::unload(kValidFile));
ASSERT_THROW(plugin::load(kValidFile, kInvalidArg), type::InvalidArgument);
ASSERT_NO_THROW(common::line(kValidArg)); // File is still loaded
ASSERT_NO_THROW(plugin::unload(kValidFile, kValidArg + " e.example1();"));
ASSERT_THROW(common::line(kValidArg + " e.example1();"), type::RuntimeError);
}
TEST_F(PluginCommands, LoadUnloadSinglePluggable)
{
ASSERT_NO_THROW(
plugin::load(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{
kValidArg + " e.example1();", kValidArg + " /* unload code */"}));
ASSERT_NO_THROW(
plugin::unload(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{
kValidArg + " /* load code */", kValidArg + " e.example2();"}));
ASSERT_THROW(
plugin::load(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{kInvalidArg, kInvalidArg}),
type::InvalidArgument);
ASSERT_NO_THROW(common::line(kValidArg));
ASSERT_NO_THROW(
plugin::unload(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{
kValidArg + " /* load code */", kValidArg + " e.example2();"}));
ASSERT_THROW(common::line(kValidArg + " e.example1();"), type::RuntimeError);
ASSERT_THROW(common::line(kValidArg + " e.example2();"), type::RuntimeError);
}
// TODO(afiore): multiple load
TEST_F(PluginCommands, ReloadSingleInvalidFile)
{
ASSERT_THROW(plugin::load(kInvalidFile), type::RuntimeError);
ASSERT_THROW(common::line(kValidArg), type::RuntimeError);
ASSERT_THROW(plugin::reload(kInvalidFile), type::RuntimeError);
ASSERT_THROW(plugin::unload(kInvalidFile), type::RuntimeError);
}
TEST_F(PluginCommands, ReloadSingleSimpleNoArg)
{
ASSERT_NO_THROW(plugin::load(kValidFile));
ASSERT_NO_THROW(common::line(kValidArg));
ASSERT_NO_THROW(plugin::reload(kValidFile));
ASSERT_NO_THROW(common::line(kValidArg));
ASSERT_NO_THROW(plugin::unload(kValidFile));
}
TEST_F(PluginCommands, ReloadSingleSimpleWithArg)
{
ASSERT_NO_THROW(plugin::load(kValidFile, kValidArg + " e.example1();"));
ASSERT_NO_THROW(
plugin::reload(
kValidFile,
{kValidArg + " /* load code */", kValidArg + " /* unload code */"}));
ASSERT_THROW(
plugin::reload(kValidFile, {" /* valid load code */", kInvalidArg}),
type::InvalidArgument);
ASSERT_THROW(
plugin::reload(kValidFile, {kInvalidArg, " /* valid unload code */"}),
type::InvalidArgument);
ASSERT_NO_THROW(common::line(kValidArg));
ASSERT_NO_THROW(
plugin::reload(
kValidFile,
{kValidArg + " /* load code */", kValidArg + " /* unload code */"}));
ASSERT_NO_THROW(plugin::unload(kValidFile, kValidArg + " e.example1();"));
ASSERT_THROW(common::line(kValidArg + " e.example1();"), type::RuntimeError);
}
TEST_F(PluginCommands, ReloadSinglePluggable)
{
ASSERT_NO_THROW(
plugin::load(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{
kValidArg + " e.example1();", kValidArg + " /* unload code */"}));
ASSERT_NO_THROW(
plugin::reload(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{
kValidArg + " e.example1();", kValidArg + " e.example2();"}));
ASSERT_THROW(
plugin::load(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{kInvalidArg, kInvalidArg}),
type::InvalidArgument);
ASSERT_NO_THROW(common::line(kValidArg));
ASSERT_NO_THROW(
plugin::reload(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{
kValidArg + " e.example1();", kValidArg + " e.example2();"}));
ASSERT_NO_THROW(common::line(kValidArg));
ASSERT_NO_THROW(
plugin::unload(
plugin::common::PluginPath{kValidFile},
plugin::common::PluginArgs{
kValidArg + " /* load code */", kValidArg + " e.example3();"}));
ASSERT_THROW(common::line(kValidArg), type::RuntimeError);
ASSERT_THROW(common::line(kValidArg + " e.example1();"), type::RuntimeError);
ASSERT_THROW(common::line(kValidArg + " e.example2();"), type::RuntimeError);
ASSERT_THROW(common::line(kValidArg + " e.example3();"), type::RuntimeError);
}
TEST_F(PluginCommands, LoaderNPI)
{
using t_path = plugin::common::PluginPath;
using t_space = plugin::common::PluginSpace;
using t_args = plugin::common::PluginArgs;
::dfi::common::type::Pluggable<t_path, t_space, t_args> type{
t_path{kValidFile},
t_space{"example"}, // inner
t_args{kValidArg + " e.example1();", kValidArg + " e.example2();"}};
plugin::common::Plugin plugin{type};
ASSERT_NO_THROW(plugin.load().reload().unload());
}
} // namespace unit
} // namespace tests
} // namespace dfi