From 7b81970330115a3f1f8b115fba5050ef7f97a3a3 Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Sun, 7 Dec 2025 13:25:59 -0800 Subject: [PATCH] container: root: test: unit: add pluggable cases Focuses on base pluggables, pluggable data types and functionality. --- container/src/root/test/common/type.hh | 1 + container/src/root/test/unit/type.hh | 92 +++++ container/src/root/test/unit/utility.hh | 527 +++++++++++++++++++++++- 3 files changed, 604 insertions(+), 16 deletions(-) diff --git a/container/src/root/test/common/type.hh b/container/src/root/test/common/type.hh index 9a6fb24..9343414 100644 --- a/container/src/root/test/common/type.hh +++ b/container/src/root/test/common/type.hh @@ -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 diff --git a/container/src/root/test/unit/type.hh b/container/src/root/test/unit/type.hh index 15d30aa..6f7f432 100644 --- a/container/src/root/test/unit/type.hh +++ b/container/src/root/test/unit/type.hh @@ -25,6 +25,7 @@ #include +#include #include #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 diff --git a/container/src/root/test/unit/utility.hh b/container/src/root/test/unit/utility.hh index 91e9b17..0092e9b 100644 --- a/container/src/root/test/unit/utility.hh +++ b/container/src/root/test/unit/utility.hh @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -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();"}; }; -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 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