// ============================================================================= // Tests for CompositeModuleLoader — verifies that it correctly delegates to its // ModuleContainer and ModuleFormatLoader, producing a valid ModuleLoader. // // Uses fake/stub implementations to test the composition logic in isolation. // No real processes are spawned. // ============================================================================= #include #include "composite_module_loader.h" #include #include #include #include #include #include using namespace LogosCore; // --------------------------------------------------------------------------- // Stubs — defined outside anonymous namespace to avoid Clang // std::make_shared / compressed_pair issues with internal-linkage types. // --------------------------------------------------------------------------- struct FakeContainer : public ModuleContainer { std::string id() const override { return "fake-container"; } bool canHandle(const ModuleDescriptor&) const override { return containerCanHandle; } bool launch(const ModuleDescriptor& desc, const std::string& hostBinary, const std::vector& args, std::function, LoadedModuleHandle& out) override { launchCalls.push_back({desc.name, hostBinary, args}); if (!launchShouldSucceed) return false; out.name = desc.name; out.pid = 42; activeModules.insert(desc.name); return true; } bool sendToken(const std::string& name, const std::string& token) override { sendTokenCalls.push_back({name, token}); return sendTokenResult; } void terminate(const std::string& name) override { terminateCalls.push_back(name); activeModules.erase(name); } void terminateAll() override { terminateAllCount++; activeModules.clear(); } bool hasModule(const std::string& name) const override { return activeModules.count(name) > 0; } std::optional pid(const std::string& name) const override { if (activeModules.count(name)) return 42; return std::nullopt; } std::unordered_map getAllPids() const override { std::unordered_map pids; for (const auto& m : activeModules) pids[m] = 42; return pids; } struct LaunchRecord { std::string name; std::string hostBinary; std::vector args; }; bool containerCanHandle = true; bool launchShouldSucceed = true; bool sendTokenResult = true; int terminateAllCount = 0; std::vector launchCalls; std::vector> sendTokenCalls; std::vector terminateCalls; std::unordered_set activeModules; }; struct FakeLoader : public ModuleFormatLoader { std::string id() const override { return "fake-loader"; } bool canHandle(const ModuleDescriptor&) const override { return loaderCanHandle; } std::string resolveHostBinary(const ModuleDescriptor&) const override { return hostBinary; } std::vector buildArguments(const ModuleDescriptor& desc) const override { return {"--name", desc.name, "--path", desc.path}; } bool loaderCanHandle = true; std::string hostBinary = "/usr/bin/fake_host"; }; class CompositeModuleLoaderTest : public ::testing::Test { protected: std::shared_ptr container; std::shared_ptr loader; std::shared_ptr composite; void SetUp() override { container.reset(new FakeContainer); loader.reset(new FakeLoader); std::shared_ptr c = container; std::shared_ptr l = loader; composite.reset(new CompositeModuleLoader(c, l)); } }; // --------------------------------------------------------------------------- // id // --------------------------------------------------------------------------- TEST_F(CompositeModuleLoaderTest, Id_CombinesLoaderAndContainerIds) { EXPECT_EQ(composite->id(), "fake-loader+fake-container"); } // --------------------------------------------------------------------------- // canHandle // --------------------------------------------------------------------------- TEST_F(CompositeModuleLoaderTest, CanHandle_TrueWhenBothCanHandle) { ModuleDescriptor desc; EXPECT_TRUE(composite->canHandle(desc)); } TEST_F(CompositeModuleLoaderTest, CanHandle_FalseWhenContainerCannot) { container->containerCanHandle = false; ModuleDescriptor desc; EXPECT_FALSE(composite->canHandle(desc)); } TEST_F(CompositeModuleLoaderTest, CanHandle_FalseWhenLoaderCannot) { loader->loaderCanHandle = false; ModuleDescriptor desc; EXPECT_FALSE(composite->canHandle(desc)); } // --------------------------------------------------------------------------- // load: resolves host + builds args via loader, launches via container // --------------------------------------------------------------------------- TEST_F(CompositeModuleLoaderTest, Load_DelegatesResolveAndLaunch) { ModuleDescriptor desc; desc.name = "mod_a"; desc.path = "/lib/mod_a.so"; LoadedModuleHandle handle; bool ok = composite->load(desc, nullptr, handle); EXPECT_TRUE(ok); EXPECT_EQ(handle.name, "mod_a"); EXPECT_EQ(handle.pid, 42); ASSERT_EQ(container->launchCalls.size(), 1u); EXPECT_EQ(container->launchCalls[0].name, "mod_a"); EXPECT_EQ(container->launchCalls[0].hostBinary, "/usr/bin/fake_host"); std::vector expected = {"--name", "mod_a", "--path", "/lib/mod_a.so"}; EXPECT_EQ(container->launchCalls[0].args, expected); } TEST_F(CompositeModuleLoaderTest, Load_FailsWhenHostBinaryEmpty) { loader->hostBinary = ""; ModuleDescriptor desc; desc.name = "no_host"; LoadedModuleHandle handle; EXPECT_FALSE(composite->load(desc, nullptr, handle)); EXPECT_TRUE(container->launchCalls.empty()); } TEST_F(CompositeModuleLoaderTest, Load_FailsWhenContainerLaunchFails) { container->launchShouldSucceed = false; ModuleDescriptor desc; desc.name = "fail_launch"; LoadedModuleHandle handle; EXPECT_FALSE(composite->load(desc, nullptr, handle)); } // --------------------------------------------------------------------------- // sendToken, terminate, terminateAll delegate to container // --------------------------------------------------------------------------- TEST_F(CompositeModuleLoaderTest, SendToken_DelegatesToContainer) { EXPECT_TRUE(composite->sendToken("mod_a", "tok123")); ASSERT_EQ(container->sendTokenCalls.size(), 1u); EXPECT_EQ(container->sendTokenCalls[0].first, "mod_a"); EXPECT_EQ(container->sendTokenCalls[0].second, "tok123"); } TEST_F(CompositeModuleLoaderTest, Terminate_DelegatesToContainer) { ModuleDescriptor desc; desc.name = "to_term"; LoadedModuleHandle h; composite->load(desc, nullptr, h); composite->terminate("to_term"); ASSERT_EQ(container->terminateCalls.size(), 1u); EXPECT_EQ(container->terminateCalls[0], "to_term"); } TEST_F(CompositeModuleLoaderTest, TerminateAll_DelegatesToContainer) { composite->terminateAll(); EXPECT_EQ(container->terminateAllCount, 1); } // --------------------------------------------------------------------------- // hasModule, pid, getAllPids delegate to container // --------------------------------------------------------------------------- TEST_F(CompositeModuleLoaderTest, HasModule_DelegatesToContainer) { EXPECT_FALSE(composite->hasModule("x")); ModuleDescriptor desc; desc.name = "x"; LoadedModuleHandle h; composite->load(desc, nullptr, h); EXPECT_TRUE(composite->hasModule("x")); } TEST_F(CompositeModuleLoaderTest, Pid_DelegatesToContainer) { EXPECT_FALSE(composite->pid("x").has_value()); ModuleDescriptor desc; desc.name = "x"; LoadedModuleHandle h; composite->load(desc, nullptr, h); auto p = composite->pid("x"); ASSERT_TRUE(p.has_value()); EXPECT_EQ(*p, 42); } TEST_F(CompositeModuleLoaderTest, GetAllPids_DelegatesToContainer) { ModuleDescriptor d1; d1.name = "a"; LoadedModuleHandle h1; composite->load(d1, nullptr, h1); ModuleDescriptor d2; d2.name = "b"; LoadedModuleHandle h2; composite->load(d2, nullptr, h2); auto pids = composite->getAllPids(); EXPECT_EQ(pids.size(), 2u); EXPECT_EQ(pids.at("a"), 42); EXPECT_EQ(pids.at("b"), 42); } // --------------------------------------------------------------------------- // Container accessor // --------------------------------------------------------------------------- TEST_F(CompositeModuleLoaderTest, ContainerAccessor_ReturnsSameInstance) { EXPECT_EQ(&composite->container(), static_cast(container.get())); }