blob: 53d9e432c6e8e3f2a2889f56a2db027814148029 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <gtest/gtest.h>
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include "celix/dm/DependencyManager.h"
#include "celix_framework_factory.h"
#include "framework.h"
class DependencyManagerTestSuite : public ::testing::Test {
public:
celix_framework_t* fw = nullptr;
celix_bundle_context_t *ctx = nullptr;
celix_properties_t *properties = nullptr;
DependencyManagerTestSuite() {
properties = celix_properties_create();
celix_properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true");
celix_properties_setBool(properties, CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE, true);
celix_properties_set(properties, CELIX_FRAMEWORK_CACHE_DIR, ".cacheBundleContextTestFramework");
celix_properties_set(properties, "CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace");
fw = celix_frameworkFactory_createFramework(properties);
ctx = framework_getContext(fw);
}
~DependencyManagerTestSuite() override {
celix_frameworkFactory_destroyFramework(fw);
}
DependencyManagerTestSuite(DependencyManagerTestSuite&&) = delete;
DependencyManagerTestSuite(const DependencyManagerTestSuite&) = delete;
DependencyManagerTestSuite& operator=(DependencyManagerTestSuite&&) = delete;
DependencyManagerTestSuite& operator=(const DependencyManagerTestSuite&) = delete;
};
TEST_F(DependencyManagerTestSuite, DmCreateComponent) {
auto *mng = celix_bundleContext_getDependencyManager(ctx);
auto *cmp = celix_dmComponent_create(ctx, "test1");
celix_dependencyManager_add(mng, cmp);
ASSERT_EQ(1, celix_dependencyManager_nrOfComponents(mng));
ASSERT_TRUE(celix_dependencyManager_allComponentsActive(mng));
cmp = celix_dmComponent_create(ctx, "test2");
celix_dependencyManager_add(mng, cmp);
ASSERT_EQ(2, celix_dependencyManager_nrOfComponents(mng));
ASSERT_TRUE(celix_dependencyManager_allComponentsActive(mng));
}
TEST_F(DependencyManagerTestSuite, DmComponentAddRemove) {
auto *mng = celix_bundleContext_getDependencyManager(ctx);
auto *cmp = celix_dmComponent_create(ctx, "test1");
celix_dependencyManager_add(mng, cmp);
ASSERT_EQ(1, celix_dependencyManager_nrOfComponents(mng));
celix_dependencyManager_remove(mng, cmp);
ASSERT_EQ(0, celix_dependencyManager_nrOfComponents(mng));
auto *cmp2 = celix_dmComponent_create(ctx, "test2");
auto *cmp3 = celix_dmComponent_create(ctx, "test3");
celix_dependencyManager_add(mng, cmp2);
celix_dependencyManager_add(mng, cmp3);
ASSERT_EQ(2, celix_dependencyManager_nrOfComponents(mng));
celix_dependencyManager_removeAllComponents(mng);
ASSERT_EQ(0, celix_dependencyManager_nrOfComponents(mng));
}
TEST_F(DependencyManagerTestSuite, DmComponentsWithConfiguredDestroyFunction) {
//Given a simple component implementation (only a name field)
struct CmpImpl {
std::string name;
};
void(*destroyFn)(CmpImpl*) = [](CmpImpl* impl) {
std::cout << "Destroying " << impl->name << std::endl;
delete impl;
};
//When 10 of these components impls are created and configured (include destroy impl function)
//as component in the DM.
auto* dm = celix_bundleContext_getDependencyManager(ctx);
for (int i = 0; i < 10; ++i) {
auto* impl = new CmpImpl{std::string{"Component"} + std::to_string(i+1)};
auto* dmCmp = celix_dmComponent_create(ctx, impl->name.c_str());
celix_dmComponent_setImplementation(dmCmp, impl);
CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(dmCmp, CmpImpl, destroyFn);
celix_dependencyManager_addAsync(dm, dmCmp);
}
//Then all component should become activate (note components with no svc dependencies)
celix_dependencyManager_wait(dm);
celix_dependencyManager_allComponentsActive(dm);
EXPECT_EQ(celix_dependencyManager_nrOfComponents(dm), 10);
//When all 10 components are removed
celix_dependencyManager_removeAllComponents(dm);
//Then all components should be removed
EXPECT_EQ(celix_dependencyManager_nrOfComponents(dm), 0);
//And when the test goes out of scope, no memory should be leaked
//nop
}
TEST_F(DependencyManagerTestSuite, DmComponentAddRemoveAsync) {
auto *mng = celix_bundleContext_getDependencyManager(ctx);
auto *cmp1 = celix_dmComponent_create(ctx, "test1");
celix_dependencyManager_addAsync(mng, cmp1);
celix_dependencyManager_wait(mng);
EXPECT_EQ(1, celix_dependencyManager_nrOfComponents(mng));
std::atomic<std::size_t> count{0};
auto cb = [](void *data) {
auto* c = static_cast<std::atomic<std::size_t>*>(data);
c->fetch_add(1);
};
celix_dependencyManager_removeAsync(mng, cmp1, &count, cb);
celix_dependencyManager_wait(mng);
EXPECT_EQ(0, celix_dependencyManager_nrOfComponents(mng));
EXPECT_EQ(1, count.load());
}
TEST_F(DependencyManagerTestSuite, DmComponentRemoveAllAsync) {
auto *mng = celix_bundleContext_getDependencyManager(ctx);
auto *cmp1 = celix_dmComponent_create(ctx, "test1");
auto *cmp2 = celix_dmComponent_create(ctx, "test2");
celix_dependencyManager_add(mng, cmp1);
celix_dependencyManager_add(mng, cmp2);
EXPECT_EQ(2, celix_dependencyManager_nrOfComponents(mng));
std::atomic<std::size_t> count{0};
auto callback = [](void *data) {
auto* c = static_cast<std::atomic<std::size_t>*>(data);
c->fetch_add(1);
};
celix_dependencyManager_removeAllComponentsAsync(mng, &count, callback);
celix_dependencyManager_wait(mng);
EXPECT_EQ(0, celix_dependencyManager_nrOfComponents(mng));
EXPECT_EQ(1, count.load());
//call again if all components are removed -> should call callback
celix_dependencyManager_removeAllComponentsAsync(mng, &count, callback);
celix_dependencyManager_wait(mng);
EXPECT_EQ(0, celix_dependencyManager_nrOfComponents(mng));
EXPECT_EQ(2, count.load());
}
TEST_F(DependencyManagerTestSuite, InvalidAddInterface) {
auto* cmp = celix_dmComponent_create(ctx, "test");
void* dummyInterfacePointer = (void*)0x42;
auto status = celix_dmComponent_addInterface(cmp, "", nullptr, dummyInterfacePointer, nullptr);
EXPECT_NE(status, CELIX_SUCCESS);
status = celix_dmComponent_addInterface(cmp, nullptr, nullptr, dummyInterfacePointer, nullptr);
EXPECT_NE(status, CELIX_SUCCESS);
celix_dmComponent_destroy(cmp);
}
TEST_F(DependencyManagerTestSuite, DmGetInfo) {
auto* mng = celix_bundleContext_getDependencyManager(ctx);
auto* cmp = celix_dmComponent_create(ctx, "test1");
void* dummyInterfacePointer = (void*)0x42;
auto* p = celix_properties_create();
celix_properties_set(p, "key", "value");
celix_dmComponent_addInterface(cmp, "test-interface", nullptr, dummyInterfacePointer, p);
auto* dep = celix_dmServiceDependency_create();
celix_dmServiceDependency_setService(dep, "test-interface", nullptr, nullptr);
celix_dmComponent_addServiceDependency(cmp, dep);
celix_dependencyManager_add(mng, cmp);
auto* infos = celix_dependencyManager_createInfos(mng);
ASSERT_EQ(1, celix_arrayList_size(infos));
auto* dmInfo = (celix_dependency_manager_info_t*)celix_arrayList_get(infos, 0);
ASSERT_EQ(1, celix_arrayList_size(dmInfo->components));
auto *info = (celix_dm_component_info_t*)celix_arrayList_get(dmInfo->components, 0);
EXPECT_EQ(1, celix_arrayList_size(info->interfaces));
auto *intInf = (dm_interface_info_t*)celix_arrayList_get(info->interfaces, 0);
EXPECT_EQ(2, celix_properties_size(intInf->properties)); //key and component.uuid
EXPECT_EQ(1, celix_arrayList_size(info->dependency_list));
celix_arrayList_destroy(infos);
}
TEST_F(DependencyManagerTestSuite, DmInterfaceAddRemove) {
auto *mng = celix_bundleContext_getDependencyManager(ctx);
auto *cmp = celix_dmComponent_create(ctx, "test1");
void *dummyInterfacePointer = (void *) 0x42;
auto *p = celix_properties_create();
celix_properties_set(p, "key", "value");
celix_dmComponent_addInterface(cmp, "test-interface", nullptr, dummyInterfacePointer, p);
auto *dep = celix_dmServiceDependency_create();
celix_dmServiceDependency_setService(dep, "test-interface", nullptr, nullptr);
celix_dmComponent_addServiceDependency(cmp, dep);
celix_dependencyManager_add(mng, cmp);
celix_dmComponent_removeInterface(cmp, dummyInterfacePointer);
auto *infos = celix_dependencyManager_createInfos(mng);
ASSERT_EQ(1, celix_arrayList_size(infos));
auto *dmInfo = (celix_dependency_manager_info_t *) celix_arrayList_get(infos, 0);
ASSERT_EQ(1, celix_arrayList_size(dmInfo->components));
auto *info = (celix_dm_component_info_t *) celix_arrayList_get(dmInfo->components, 0);
EXPECT_EQ(0, celix_arrayList_size(info->interfaces));
celix_arrayList_destroy(infos);
}
TEST_F(DependencyManagerTestSuite, TestCheckActive) {
auto *mng = celix_bundleContext_getDependencyManager(ctx);
auto *cmp = celix_dmComponent_create(ctx, "test1");
auto *dep = celix_dmServiceDependency_create();
celix_dmServiceDependency_setService(dep, "svcname", nullptr, nullptr);
celix_dmServiceDependency_setRequired(dep, true);
celix_dmComponent_addServiceDependency(cmp, dep); //required dep -> cmp not active
celix_dependencyManager_add(mng, cmp);
ASSERT_FALSE(celix_dependencyManager_areComponentsActive(mng));
}
class TestComponent {
};
struct TestService {
void *handle = nullptr;
};
class Cmp1 : public TestService {
};
class Cmp2 : public TestService {
public:
explicit Cmp2(const std::string& name) {
std::cout << "usage arg: " << name << std::endl;
}
};
class Cmp3 /*note no inherit*/ {
};
TEST_F(DependencyManagerTestSuite, CxxDmGetInfo) {
celix::dm::DependencyManager mng{ctx};
auto& cmp = mng.createComponent<Cmp1>();
cmp.createProvidedService<TestService>().addProperty("key", "value");
cmp.createServiceDependency<TestService>().setVersionRange("[1,2)").setRequired(true);
auto infos = mng.getInfos();
EXPECT_EQ(infos.size(), 1);
auto info = mng.getInfo();
EXPECT_EQ(info.bndId, 0);
EXPECT_EQ(info.bndSymbolicName, "apache_celix_framework");
EXPECT_EQ(info.components.size(), 0); //not build yet
mng.build();
info = mng.getInfo(); //new info
ASSERT_EQ(info.components.size(), 1); //build
auto& cmpInfo = info.components[0];
EXPECT_TRUE(!cmpInfo.uuid.empty());
EXPECT_EQ(cmpInfo.name, "Cmp1");
EXPECT_EQ(cmpInfo.state, "WAITING_FOR_REQUIRED");
EXPECT_FALSE(cmpInfo.isActive);
EXPECT_EQ(cmpInfo.nrOfTimesStarted, 0);
EXPECT_EQ(cmpInfo.nrOfTimesResumed, 0);
ASSERT_EQ(cmpInfo.interfacesInfo.size(), 1);
auto& intf = cmpInfo.interfacesInfo[0];
EXPECT_EQ(intf.serviceName, "TestService");
EXPECT_TRUE(!intf.properties.empty());
EXPECT_NE(intf.properties.find("key"), intf.properties.end());
ASSERT_EQ(cmpInfo.dependenciesInfo.size(), 1);
auto& dep = cmpInfo.dependenciesInfo[0];
EXPECT_EQ(dep.serviceName, "TestService");
EXPECT_EQ(dep.isRequired, true);
EXPECT_EQ(dep.versionRange, "[1,2)");
EXPECT_EQ(dep.isAvailable, false);
EXPECT_EQ(dep.nrOfTrackedServices, 0);
}
TEST_F(DependencyManagerTestSuite, OnlyActiveAfterBuildCheck) {
celix::dm::DependencyManager dm{ctx};
EXPECT_EQ(0, dm.getNrOfComponents());
auto& cmp = dm.createComponent<TestComponent>(std::make_shared<TestComponent>(), "test1");
EXPECT_EQ(0, dm.getNrOfComponents()); //dm not started yet / comp not build yet
EXPECT_TRUE(cmp.isValid());
cmp.build();
cmp.build(); //should be ok to call twice
EXPECT_EQ(1, dm.getNrOfComponents()); //cmp "build", so active
dm.clear();
dm.clear(); //should be ok to call twice
EXPECT_EQ(0, dm.getNrOfComponents()); //dm cleared so no components
}
TEST_F(DependencyManagerTestSuite, StartDmWillBuildCmp) {
celix::dm::DependencyManager dm{ctx};
EXPECT_EQ(0, dm.getNrOfComponents());
auto& cmp = dm.createComponent<TestComponent>(std::make_shared<TestComponent>(), "test1");
EXPECT_EQ(0, dm.getNrOfComponents()); //dm not started yet / comp not build yet
EXPECT_TRUE(cmp.isValid());
dm.start();
EXPECT_EQ(1, dm.getNrOfComponents()); //cmp "build", so active
dm.stop();
EXPECT_EQ(0, dm.getNrOfComponents()); //dm cleared so no components
}
TEST_F(DependencyManagerTestSuite, CreateComponentVariant) {
celix::dm::DependencyManager dm{ctx};
dm.createComponent<Cmp1>().addInterface<TestService>(); //lazy
dm.createComponent(std::unique_ptr<Cmp1>{new Cmp1}).addInterface<TestService>(); //with unique ptr
dm.createComponent(std::make_shared<Cmp1>()).addInterface<TestService>(); //with shared ptr
dm.createComponent(Cmp1{}).addInterface<TestService>(); //with value
//dm.createComponent<Cmp2>(); //Does not compile -> no default ctor
dm.createComponent(std::unique_ptr<Cmp2>{new Cmp2{"a"}}).addInterface<TestService>(); //with unique ptr
dm.createComponent(std::make_shared<Cmp2>("b")).addInterface<TestService>();; //with shared ptr
dm.createComponent(Cmp2{"c"}).addInterface<TestService>();; //with value
dm.start();
}
TEST_F(DependencyManagerTestSuite, AddSvcProvideAfterBuild) {
celix::dm::DependencyManager dm{ctx};
EXPECT_EQ(0, dm.getNrOfComponents());
auto& cmp = dm.createComponent<TestComponent>(std::make_shared<TestComponent>(), "test1");
EXPECT_EQ(0, dm.getNrOfComponents()); //dm not started yet / comp not build yet
EXPECT_TRUE(cmp.isValid());
cmp.build();
EXPECT_EQ(1, dm.getNrOfComponents()); //cmp "build", so active
TestService svc{};
cmp.addCInterface(&svc, "TestService");
long svcId = celix_bundleContext_findService(ctx, "TestService");
EXPECT_EQ(-1, svcId); //not build -> not found
cmp.build();
cmp.build(); //should be ok to call twice
svcId = celix_bundleContext_findService(ctx, "TestService");
EXPECT_GT(svcId, -1); //(re)build -> found
dm.clear();
dm.wait();
dm.waitIfAble();
EXPECT_EQ(0, dm.getNrOfComponents()); //dm cleared so no components
svcId = celix_bundleContext_findService(ctx, "TestService");
EXPECT_EQ(svcId, -1); //cleared -> not found
}
TEST_F(DependencyManagerTestSuite, BuildSvcProvide) {
celix::dm::DependencyManager dm{ctx};
EXPECT_EQ(0, dm.getNrOfComponents());
auto& cmp = dm.createComponent<Cmp1>(std::make_shared<Cmp1>(), "test2");
EXPECT_EQ(0, dm.getNrOfComponents()); //dm not started yet / comp not build yet
EXPECT_TRUE(cmp.isValid());
cmp.build();
EXPECT_EQ(1, dm.getNrOfComponents()); //cmp "build", so active
TestService svc{};
cmp.createProvidedCService(&svc, "CTestService").addProperty("key1", "val1").addProperty("key2", 3);
long svcId = celix_bundleContext_findService(ctx, "CTestService");
EXPECT_EQ(-1, svcId); //not build -> not found
cmp.build();
cmp.build(); //should be ok to call twice
dm.wait();
svcId = celix_bundleContext_findService(ctx, "CTestService");
EXPECT_GT(svcId, -1); //(re)build -> found
celix_service_filter_options_t opts{};
opts.serviceName = "CTestService";
opts.filter = "(&(key1=val1)(key2=3))";
svcId = celix_bundleContext_findServiceWithOptions(ctx, &opts);
EXPECT_GT(svcId, -1); //found, so properties present
celix::dm::Properties props{};
props["key1"] = "value";
cmp.createProvidedService<TestService>().setProperties(props).setVersion("1.0.0").build();
opts.serviceName = "TestService";
opts.filter = "(key1=value)";
svcId = celix_bundleContext_findServiceWithOptions(ctx, &opts);
EXPECT_GT(svcId, -1); //found, so properties present
dm.clear();
dm.wait();
EXPECT_EQ(0, dm.getNrOfComponents()); //dm cleared so no components
svcId = celix_bundleContext_findService(ctx, "CTestService");
EXPECT_EQ(svcId, -1); //cleared -> not found
}
TEST_F(DependencyManagerTestSuite, BuildUnassociatedProvidedService) {
celix::dm::DependencyManager dm{ctx};
//Given a component which does not inherit any interfaces
auto& cmp = dm.createComponent<Cmp1>(std::make_shared<Cmp1>());
//Then I can create a provided service using a shared_ptr which is not associated with the component type
// (TestService is not a base of Cmp3)
cmp.createUnassociatedProvidedService(std::make_shared<TestService>())
.addProperty("test1", "value1");
//And I can create a provided service using a shared_ptr and a custom name
cmp.createUnassociatedProvidedService(std::make_shared<TestService>(), "CustomName");
//When I build the component
cmp.build();
//Then the nr of component is 1
ASSERT_EQ(1, dm.getNrOfComponents()); //cmp "build", so active
//And the nr of provided interfaces of that component is 2
auto info = dm.getInfo();
ASSERT_EQ(info.components[0].interfacesInfo.size(), 2);
//And the first (index 0) provided service has a name TestService and a property test1 with value value1
EXPECT_STREQ(info.components[0].interfacesInfo[0].serviceName.c_str(), "TestService");
EXPECT_STREQ(info.components[0].interfacesInfo[0].properties["test1"].c_str(), "value1");
//And the second (index 1) provide service has a name "CustomName".
EXPECT_STREQ(info.components[0].interfacesInfo[1].serviceName.c_str(), "CustomName");
}
TEST_F(DependencyManagerTestSuite, AddSvcDepAfterBuild) {
celix::dm::DependencyManager dm{ctx};
EXPECT_EQ(0, dm.getNrOfComponents());
auto& cmp = dm.createComponent<TestComponent>(std::make_shared<TestComponent>(), "test1");
EXPECT_EQ(0, dm.getNrOfComponents()); //dm not started yet / comp not build yet
EXPECT_TRUE(cmp.isValid());
cmp.build();
cmp.build(); //should be ok to call twice
EXPECT_EQ(1, dm.getNrOfComponents()); //cmp "build", so active
std::atomic<int> count{0};
auto& dep = cmp.createCServiceDependency<TestService>("TestService")
.setCallbacks([&count](const TestService*, celix::dm::Properties&&) {
count++;
});
TestService svc{};
long svcId = celix_bundleContext_registerService(ctx, &svc, "TestService", nullptr);
long svcId2 = celix_bundleContext_registerService(ctx, &svc, "AnotherService", nullptr); //note should not be found.
ASSERT_EQ(0, count); //service dep not yet build -> so no set call
dep.build();
dep.build(); //should be ok to call twice
celix_bundleContext_waitForEvents(ctx);
ASSERT_EQ(1, count); //service dep build -> so count is 1;
//create another service dep
cmp.createCServiceDependency<TestService>("TestService")
.setCallbacks([&count](const TestService*, celix::dm::Properties&&) {
count++;
});
ASSERT_EQ(1, count); //new service dep not yet build -> so count still 1
cmp.build(); //cmp build, which will build svc dep
ASSERT_EQ(2, count); //new service dep build -> so count is 2
celix_bundleContext_unregisterService(ctx, svcId);
celix_bundleContext_unregisterService(ctx, svcId2);
}
TEST_F(DependencyManagerTestSuite, InCompleteBuildShouldNotLeak) {
celix::dm::DependencyManager dm{ctx};
dm.createComponent<TestComponent>(std::make_shared<TestComponent>(), "test1"); //NOTE NOT BUILD
auto& cmp2 = dm.createComponent<Cmp1>(std::make_shared<Cmp1>(), "test2").build(); //NOTE BUILD
cmp2.createCServiceDependency<TestService>("TestService").setFilter("(key=value"); //note not build
cmp2.createServiceDependency<TestService>().setFilter("(key=value)").setName("alternative name"); //note not build
TestService svc{};
cmp2.createProvidedCService(&svc, "CTestService").addProperty("key1", "val1"); //note not build
cmp2.createProvidedService<TestService>().setVersion("1.0.0"); //note not build
}
TEST_F(DependencyManagerTestSuite, RemoveAndClear) {
celix::dm::DependencyManager dm{ctx};
auto& cmp1 = dm.createComponent<TestComponent>(std::make_shared<TestComponent>()).build();
auto& cmp2 = dm.createComponent<TestComponent>(std::make_shared<TestComponent>()).build();
dm.createComponent<TestComponent>(std::make_shared<TestComponent>()).build();
auto& cmp4 = dm.createComponent<TestComponent>(std::make_shared<TestComponent>());
dm.wait();
dm.destroyComponent(cmp1);
bool removed = dm.removeComponent(cmp2.getUUID());
EXPECT_TRUE(removed);
removed = dm.removeComponentAsync(cmp4.getUUID());
EXPECT_TRUE(removed);
dm.clear();
}
TEST_F(DependencyManagerTestSuite, RequiredDepsAreInjectedDuringStartStop) {
class LifecycleComponent {
public:
void start() {
std::lock_guard<std::mutex> lck{mutex};
EXPECT_TRUE(setSvc != nullptr);
EXPECT_EQ(services.size(), 1);
}
void stop() {
std::lock_guard<std::mutex> lck{mutex};
EXPECT_TRUE(setSvc != nullptr);
EXPECT_EQ(services.size(), 1);
}
void setService(TestService* svc) {
std::lock_guard<std::mutex> lck{mutex};
setSvc = svc;
}
void addService(TestService* svc) {
std::lock_guard<std::mutex> lck{mutex};
services.emplace_back(svc);
}
void remService(TestService* svc) {
std::lock_guard<std::mutex> lck{mutex};
for (auto it = services.begin(); it != services.end(); ++it) {
if (*it == svc) {
services.erase(it);
break;
}
}
}
private:
std::mutex mutex{};
TestService* setSvc{};
std::vector<TestService*> services{};
};
celix::dm::DependencyManager dm{ctx};
auto& cmp = dm.createComponent<LifecycleComponent>()
.setCallbacks(nullptr, &LifecycleComponent::start, &LifecycleComponent::stop, nullptr);
cmp.createServiceDependency<TestService>()
.setRequired(true)
.setCallbacks(&LifecycleComponent::setService)
.setCallbacks(&LifecycleComponent::addService, &LifecycleComponent::remService);
cmp.build();
TestService svc;
std::string svcName = celix::typeName<TestService>();
celix_service_registration_options opts{};
opts.svc = &svc;
opts.serviceName = svcName.c_str();
long svcId = celix_bundleContext_registerServiceWithOptions(dm.bundleContext(), &opts);
EXPECT_GE(svcId, 0);
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
celix_bundleContext_unregisterService(dm.bundleContext(), svcId);
}
TEST_F(DependencyManagerTestSuite, RemoveOwnDependencyShouldNotLeadToDoubleStop) {
//Given a component LifecycleComponent which provides and requires a TestService service
class LifecycleComponent : public TestService {
public:
void start() {
startCount.fetch_add(1);
}
void stop() {
stopCount.fetch_add(1);
}
int getStartCount() const { return startCount.load(); }
int getStopCount() const { return stopCount.load(); }
private:
std::atomic<int> startCount{0};
std::atomic<int> stopCount{0};
};
celix::dm::DependencyManager dm{ctx};
auto lifecycleCmp = std::make_shared<LifecycleComponent>();
auto& cmp = dm.createComponent<LifecycleComponent>(lifecycleCmp)
.setCallbacks(nullptr, &LifecycleComponent::start, &LifecycleComponent::stop, nullptr);
cmp.createProvidedService<TestService>();
cmp.createServiceDependency<TestService>()
.setRequired(true);
cmp.build();
using celix::dm::ComponentState;
//Then the component state should be waiting for required
EXPECT_EQ(cmp.getState(), ComponentState::WAITING_FOR_REQUIRED);
//When a TestService is registered
TestService svc{};
long svcId = celix_bundleContext_registerService(ctx, &svc, "TestService", nullptr);
celix_bundleContext_waitForEvents(ctx);
//Then the component state should become tracking optional (active)
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
//And the start count should become 1
EXPECT_EQ(lifecycleCmp->getStartCount(), 1);
//When the TestService is unregistered
celix_bundleContext_unregisterService(ctx, svcId);
//Then the component state will stay tracking optional (because it now depends on its own provided services)
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
EXPECT_EQ(lifecycleCmp->getStopCount(), 0);
//When an additional required service dependency is added
cmp.createServiceDependency<TestService>("DummyName")
.setRequired(true)
.buildAsync();
celix_bundleContext_waitForEvents(ctx);
//Then the component state becomes waiting for required
EXPECT_EQ(cmp.getState(), ComponentState::INSTANTIATED_AND_WAITING_FOR_REQUIRED);
//And the stop count becomes 1 (not 2 becomes of a loop the dm component handling)
EXPECT_EQ(lifecycleCmp->getStopCount(), 1);
}
TEST_F(DependencyManagerTestSuite, IntermediateStatesDuringInitDeinitStartingAndStopping) {
//Given a component LifecycleComponent with an optional service dependency on TestService with a suspend-strategy
//and a required service dependency on "RequiredTestService" with a locking-strategy.
class LifecycleComponent {
public:
enum class LifecycleMethod : std::int8_t {
None = 0,
Init = 1,
Start = 2,
Stop = 3,
Deinit = 4,
};
void init() {
std::cout << "in init callback\n";
setStateAndWaitToContinue(LifecycleMethod::Init);
}
void deinit() {
std::cout << "in deinit callback\n";
setStateAndWaitToContinue(LifecycleMethod::Deinit);
}
void start() {
std::cout << "in start callback\n";
setStateAndWaitToContinue(LifecycleMethod::Start);
}
void stop() {
std::cout << "in stop callback\n";
setStateAndWaitToContinue(LifecycleMethod::Stop);
}
void setStayInMethod(LifecycleMethod m) {
std::lock_guard<std::mutex> lck{mutex};
stayInMethod = m;
cond.notify_all();
}
void waitUntilInMethod(LifecycleMethod m) {
std::unique_lock<std::mutex> lck{mutex};
cond.wait_for(lck, std::chrono::seconds{5}, [&]{ return currentMethod == m; });
}
private:
void setStateAndWaitToContinue(LifecycleMethod m) {
std::unique_lock<std::mutex> lck{mutex};
currentMethod = m;
cond.notify_all();
cond.wait_for(lck, std::chrono::seconds{5}, [&] { return currentMethod != stayInMethod; });
currentMethod = LifecycleMethod::None;
cond.notify_all();
}
std::mutex mutex{};
std::condition_variable cond{};
LifecycleMethod stayInMethod = LifecycleMethod::None;
LifecycleMethod currentMethod = LifecycleMethod::None;
};
using LifecycleMethod = LifecycleComponent::LifecycleMethod;
celix::dm::DependencyManager dm{ctx};
auto lifecycleCmp = std::make_shared<LifecycleComponent>();
auto& cmp = dm.createComponent<LifecycleComponent>(lifecycleCmp)
.setCallbacks(&LifecycleComponent::init, &LifecycleComponent::start, &LifecycleComponent::stop, &LifecycleComponent::deinit);
cmp.createServiceDependency<TestService>()
.setStrategy(celix::dm::DependencyUpdateStrategy::suspend)
.setCallbacks([](std::shared_ptr<TestService> /*service*/, const std::shared_ptr<const celix::Properties>& /*properties*/){ std::cout << "Dummy set for svc callback\n"; })
.setRequired(false);
cmp.createServiceDependency<TestService>("RequiredTestService")
.setStrategy(celix::dm::DependencyUpdateStrategy::locking)
.setRequired(true);
cmp.buildAsync();
//Then the component state should become waiting for required
cmp.wait();
EXPECT_EQ(cmp.getState(), ComponentState::WAITING_FOR_REQUIRED);
//When the component should wait in the init callback
lifecycleCmp->setStayInMethod(LifecycleMethod::Init);
//And a "RequiredTestService" service is registered
TestService reqSvc{};
long reqSvcId = celix_bundleContext_registerServiceAsync(ctx, &reqSvc, "RequiredTestService", nullptr);
EXPECT_GE(reqSvcId, 0);
//Then the component state should become initializing
using celix::dm::ComponentState;
lifecycleCmp->waitUntilInMethod(LifecycleMethod::Init);
EXPECT_EQ(cmp.getState(), ComponentState::INITIALIZING);
//When the component should wait in the start callback
//Then the component state should become starting
lifecycleCmp->setStayInMethod(LifecycleMethod::Start);
lifecycleCmp->waitUntilInMethod(LifecycleMethod::Start);
EXPECT_EQ(cmp.getState(), ComponentState::STARTING);
//When the component should not wait in the lifecycle callbacks
lifecycleCmp->setStayInMethod(LifecycleMethod::None);
//Then the component state should become tracking optional
cmp.wait();
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
//When the component should wait in the stop callback
lifecycleCmp->setStayInMethod(LifecycleMethod::Stop);
//And a new TestService is registered (leading to a suspending of the component)
TestService svc;
std::string svcName = celix::typeName<TestService>();
celix_service_registration_options opts{};
opts.svc = &svc;
opts.serviceName = svcName.c_str();
long optionalSvcId = celix_bundleContext_registerServiceWithOptionsAsync(dm.bundleContext(), &opts);
EXPECT_GE(optionalSvcId, 0);
//Then the component state should become suspending
lifecycleCmp->waitUntilInMethod(LifecycleMethod::Stop);
EXPECT_EQ(cmp.getState(), ComponentState::SUSPENDING);
//When the component should wait in the start callback
//Then the component state should become resuming (suspending->suspended->resuming)
lifecycleCmp->setStayInMethod(LifecycleMethod::Start);
lifecycleCmp->waitUntilInMethod(LifecycleMethod::Start);
EXPECT_EQ(cmp.getState(), ComponentState::RESUMING);
//When the component should not wait in the lifecycle callbacks
lifecycleCmp->setStayInMethod(LifecycleMethod::None);
//Then the component state should become tracking optional
cmp.wait();
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
//When the component should wait in the stop callback
lifecycleCmp->setStayInMethod(LifecycleMethod::Stop);
//And the "RequiredTestService" is unregistered
celix_bundleContext_unregisterServiceAsync(ctx, reqSvcId, nullptr, nullptr);
//Then the component state should become stopping
lifecycleCmp->waitUntilInMethod(LifecycleMethod::Stop);
EXPECT_EQ(cmp.getState(), ComponentState::STOPPING);
//When the component should not wait in the lifecycle callbacks
lifecycleCmp->setStayInMethod(LifecycleMethod::None);
//Then the component state should become instantiated and waiting for required
cmp.wait();
EXPECT_EQ(cmp.getState(), ComponentState::INSTANTIATED_AND_WAITING_FOR_REQUIRED);
//When the component should wait in the deinit callback
lifecycleCmp->setStayInMethod(LifecycleMethod::Deinit);
//And the component is removed from the dependency manager
dm.removeComponentAsync(cmp.getUUID());
//Then the component state should become deinitializing
lifecycleCmp->waitUntilInMethod(LifecycleMethod::Deinit);
EXPECT_EQ(cmp.getState(), ComponentState::DEINITIALIZING);
lifecycleCmp->setStayInMethod(LifecycleMethod::None);
celix_bundleContext_unregisterService(dm.bundleContext(), optionalSvcId);
}
TEST_F(DependencyManagerTestSuite, DepsAreInjectedAsSharedPointers) {
class LifecycleComponent {
public:
void start() {
std::lock_guard<std::mutex> lck{mutex};
EXPECT_TRUE(setSvc != nullptr);
EXPECT_EQ(services.size(), 1);
}
void stop() {
std::lock_guard<std::mutex> lck{mutex};
EXPECT_TRUE(setSvc != nullptr);
EXPECT_EQ(services.size(), 1);
}
void setService(const std::shared_ptr<TestService>& svc, const std::shared_ptr<const celix::Properties>& /*props*/) {
std::lock_guard<std::mutex> lck{mutex};
setSvc = svc;
}
void addService(const std::shared_ptr<TestService>& svc, const std::shared_ptr<const celix::Properties>& props) {
EXPECT_TRUE(props);
std::lock_guard<std::mutex> lck{mutex};
services.emplace_back(svc);
}
void remService(const std::shared_ptr<TestService>& svc, const std::shared_ptr<const celix::Properties>& props) {
EXPECT_TRUE(props);
std::lock_guard<std::mutex> lck{mutex};
for (auto it = services.begin(); it != services.end(); ++it) {
if (*it == svc) {
services.erase(it);
break;
}
}
}
private:
std::mutex mutex{};
std::shared_ptr<TestService> setSvc{};
std::vector<std::shared_ptr<TestService>> services{};
};
celix::dm::DependencyManager dm{ctx};
auto& cmp = dm.createComponent<LifecycleComponent>()
.setCallbacks(nullptr, &LifecycleComponent::start, &LifecycleComponent::stop, nullptr);
cmp.createServiceDependency<TestService>()
.setRequired(true)
.setCallbacks(&LifecycleComponent::setService)
.setCallbacks(&LifecycleComponent::addService, &LifecycleComponent::remService);
cmp.build();
TestService svc;
std::string svcName = celix::typeName<TestService>();
celix_service_registration_options opts{};
opts.svc = &svc;
opts.serviceName = svcName.c_str();
long svcId = celix_bundleContext_registerServiceWithOptions(dm.bundleContext(), &opts);
EXPECT_GE(svcId, 0);
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
celix_bundleContext_unregisterService(dm.bundleContext(), svcId);
}
TEST_F(DependencyManagerTestSuite, DepsNoPropsAreInjectedAsSharedPointers) {
class LifecycleComponent {
public:
void start() {
std::lock_guard<std::mutex> lck{mutex};
EXPECT_TRUE(setSvc != nullptr);
EXPECT_EQ(services.size(), 1);
}
void stop() {
std::lock_guard<std::mutex> lck{mutex};
EXPECT_TRUE(setSvc != nullptr);
EXPECT_EQ(services.size(), 1);
}
void setService(const std::shared_ptr<TestService>& svc) {
std::lock_guard<std::mutex> lck{mutex};
setSvc = svc;
}
void addService(const std::shared_ptr<TestService>& svc) {
std::lock_guard<std::mutex> lck{mutex};
services.emplace_back(svc);
}
void remService(const std::shared_ptr<TestService>& svc) {
std::lock_guard<std::mutex> lck{mutex};
for (auto it = services.begin(); it != services.end(); ++it) {
if (*it == svc) {
services.erase(it);
break;
}
}
}
private:
std::mutex mutex{};
std::shared_ptr<TestService> setSvc{};
std::vector<std::shared_ptr<TestService>> services{};
};
celix::dm::DependencyManager dm{ctx};
auto& cmp = dm.createComponent<LifecycleComponent>()
.setCallbacks(nullptr, &LifecycleComponent::start, &LifecycleComponent::stop, nullptr);
cmp.createServiceDependency<TestService>()
.setRequired(true)
.setCallbacks(&LifecycleComponent::setService)
.setCallbacks(&LifecycleComponent::addService, &LifecycleComponent::remService);
cmp.build();
TestService svc;
std::string svcName = celix::typeName<TestService>();
celix_service_registration_options opts{};
opts.svc = &svc;
opts.serviceName = svcName.c_str();
long svcId = celix_bundleContext_registerServiceWithOptions(dm.bundleContext(), &opts);
EXPECT_GE(svcId, 0);
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
celix_bundleContext_unregisterService(dm.bundleContext(), svcId);
}
TEST_F(DependencyManagerTestSuite, UnneededSuspendIsPrevented) {
class CounterComponent {
public:
void start() {
startCount++;
}
void stop() {
stopCount++;
}
void setService(TestService* /*svc*/) {
//nop
}
void addService(TestService* /*svc*/) {
//nop
}
void remService(TestService* /*svc*/) {
//nop
}
std::atomic<int> startCount{0};
std::atomic<int> stopCount{0};
};
celix::dm::DependencyManager dm{ctx};
//cmp1 has lifecycle callbacks, but not set or add/rem callbacks for the service dependency -> should not trigger suspend
auto& cmp1 = dm.createComponent<CounterComponent>("CounterCmp1")
.setCallbacks(nullptr, &CounterComponent::start, &CounterComponent::stop, nullptr);
cmp1.createServiceDependency<TestService>();
cmp1.build();
//cmp2 has lifecycle callbacks and set, add/rem callbacks for the service dependency -> should trigger suspend 2x
auto& cmp2 = dm.createComponent<CounterComponent>("CounterCmp2")
.setCallbacks(nullptr, &CounterComponent::start, &CounterComponent::stop, nullptr);
cmp2.createServiceDependency<TestService>()
.setCallbacks(&CounterComponent::setService)
.setCallbacks(&CounterComponent::addService, &CounterComponent::remService);
cmp2.build();
EXPECT_EQ(cmp1.getState(), celix::dm::ComponentState::TRACKING_OPTIONAL);
EXPECT_EQ(cmp2.getState(), celix::dm::ComponentState::TRACKING_OPTIONAL);
TestService svc;
std::string svcName = celix::typeName<TestService>();
celix_service_registration_options opts{};
opts.svc = &svc;
opts.serviceName = svcName.c_str();
long svcId = celix_bundleContext_registerServiceWithOptions(dm.bundleContext(), &opts);
EXPECT_GE(svcId, 0);
EXPECT_EQ(cmp1.getInstance().startCount, 1); //only once during creation
EXPECT_EQ(cmp1.getInstance().stopCount, 0);
EXPECT_EQ(cmp2.getInstance().startCount, 3); //1x creation, 1x suspend for set, 1x suspend for add
EXPECT_EQ(cmp2.getInstance().stopCount, 2); //1x suspend for set, 1x suspend for add
cmp1.getInstance().startCount = 0;
cmp1.getInstance().stopCount = 0;
cmp2.getInstance().startCount = 0;
cmp2.getInstance().stopCount = 0;
celix_bundleContext_unregisterService(dm.bundleContext(), svcId);
EXPECT_EQ(cmp1.getInstance().startCount, 0);
EXPECT_EQ(cmp1.getInstance().stopCount, 0);
EXPECT_EQ(cmp2.getInstance().startCount, 2); //1x suspend for set nullptr, 1x suspend for rem
EXPECT_EQ(cmp2.getInstance().stopCount, 2); //1x suspend for set nullptr, 1x suspend for rem
}
TEST_F(DependencyManagerTestSuite, ExceptionsInLifecycle) {
class ExceptionComponent {
public:
enum class LifecycleMethod : std::uint8_t {
INIT,
START,
STOP,
DEINIT
};
explicit ExceptionComponent(LifecycleMethod _failAt) : failAt{_failAt} {}
void failFor(LifecycleMethod lm) {
if (lm == failAt) {
throw std::logic_error("fail test");
}
}
void init() {
failFor(LifecycleMethod::INIT);
}
void start() {
failFor(LifecycleMethod::START);
}
void stop() {
failFor(LifecycleMethod::STOP);
}
void deinit() {
failFor(LifecycleMethod::DEINIT);
}
private:
const LifecycleMethod failAt;
};
celix::dm::DependencyManager dm{ctx};
{
auto& cmp = dm.createComponent(std::make_shared<ExceptionComponent>(ExceptionComponent::LifecycleMethod::INIT), "FailAtInitCmp")
.setCallbacks(&ExceptionComponent::init, &ExceptionComponent::start, &ExceptionComponent::stop, &ExceptionComponent::deinit);
EXPECT_EQ(cmp.getState(), ComponentState::INACTIVE);
cmp.build(); //fails at init and should disable
EXPECT_EQ(cmp.getState(), ComponentState::INACTIVE);
dm.clear();
}
{
auto& cmp = dm.createComponent(std::make_shared<ExceptionComponent>(ExceptionComponent::LifecycleMethod::START), "FailAtStartCmp")
.setCallbacks(&ExceptionComponent::init, &ExceptionComponent::start, &ExceptionComponent::stop, &ExceptionComponent::deinit);
EXPECT_EQ(cmp.getState(), ComponentState::INACTIVE);
cmp.build(); //fails at init and should disable
EXPECT_EQ(cmp.getState(), ComponentState::INACTIVE);
dm.clear();
}
{
auto& cmp = dm.createComponent(std::make_shared<ExceptionComponent>(ExceptionComponent::LifecycleMethod::STOP), "FailAtStopCmp")
.setCallbacks(&ExceptionComponent::init, &ExceptionComponent::start, &ExceptionComponent::stop, &ExceptionComponent::deinit);
EXPECT_EQ(cmp.getState(), ComponentState::INACTIVE);
cmp.build();
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
//required service -> should stop, but fails at stop and should become inactive (component will disable itself)
cmp.createServiceDependency<TestService>().setRequired(true).build();
cmp.wait();
EXPECT_EQ(cmp.getState(), ComponentState::INACTIVE);
dm.clear();
}
{
auto& cmp = dm.createComponent(std::make_shared<ExceptionComponent>(ExceptionComponent::LifecycleMethod::DEINIT), "FailAtDeinit")
.setCallbacks(&ExceptionComponent::init, &ExceptionComponent::start, &ExceptionComponent::stop, &ExceptionComponent::deinit);
EXPECT_EQ(cmp.getState(), ComponentState::INACTIVE);
cmp.build();
EXPECT_EQ(cmp.getState(), ComponentState::TRACKING_OPTIONAL);
//required service -> should stop, but fails at stop and should become inactive (component will disable itself)
dm.clear(); //dm clear will deinit component and this should fail, but not deadlock
}
}
TEST_F(DependencyManagerTestSuite, ComponentContextShouldNotLeak) {
celix::dm::DependencyManager dm{ctx};
dm.createComponent<TestComponent>()
.addContext(std::make_shared<std::vector<std::string>>())
.build();
//note should not leak mem
}
TEST_F(DependencyManagerTestSuite, installBundleWithDepManActivator) {
auto* list = celix_bundleContext_listBundles(ctx);
EXPECT_EQ(celix_arrayList_size(list), 0);
celix_arrayList_destroy(list);
auto bndId = celix_bundleContext_installBundle(ctx, SIMPLE_CXX_DEP_MAN_BUNDLE_LOC, true);
EXPECT_GT(bndId, CELIX_FRAMEWORK_BUNDLE_ID);
list = celix_bundleContext_listBundles(ctx);
EXPECT_EQ(celix_arrayList_size(list), 1);
celix_arrayList_destroy(list);
}
TEST_F(DependencyManagerTestSuite, testStateToString) {
const char* state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_INACTIVE);
EXPECT_STREQ(state, "INACTIVE");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_WAITING_FOR_REQUIRED);
EXPECT_STREQ(state, "WAITING_FOR_REQUIRED");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_INITIALIZING);
EXPECT_STREQ(state, "INITIALIZING");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_DEINITIALIZING);
EXPECT_STREQ(state, "DEINITIALIZING");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_INITIALIZED_AND_WAITING_FOR_REQUIRED);
EXPECT_STREQ(state, "INITIALIZED_AND_WAITING_FOR_REQUIRED");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_STARTING);
EXPECT_STREQ(state, "STARTING");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_STOPPING);
EXPECT_STREQ(state, "STOPPING");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_TRACKING_OPTIONAL);
EXPECT_STREQ(state, "TRACKING_OPTIONAL");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_SUSPENDING);
EXPECT_STREQ(state, "SUSPENDING");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_SUSPENDED);
EXPECT_STREQ(state, "SUSPENDED");
state = celix_dmComponent_stateToString(CELIX_DM_CMP_STATE_RESUMING);
EXPECT_STREQ(state, "RESUMING");
state = celix_dmComponent_stateToString((celix_dm_component_state_enum)0);
EXPECT_STREQ(state, "INACTIVE");
state = celix_dmComponent_stateToString((celix_dm_component_state_enum)16);
EXPECT_STREQ(state, "INACTIVE");
//test deprecated dm cmp states
state = celix_dmComponent_stateToString(DM_CMP_STATE_INACTIVE);
EXPECT_STREQ(state, "INACTIVE");
state = celix_dmComponent_stateToString(DM_CMP_STATE_INSTANTIATED_AND_WAITING_FOR_REQUIRED);
EXPECT_STREQ(state, "INITIALIZED_AND_WAITING_FOR_REQUIRED");
state = celix_dmComponent_stateToString(DM_CMP_STATE_TRACKING_OPTIONAL);
EXPECT_STREQ(state, "TRACKING_OPTIONAL");
state = celix_dmComponent_stateToString(DM_CMP_STATE_WAITING_FOR_REQUIRED);
EXPECT_STREQ(state, "WAITING_FOR_REQUIRED");
}
class TestInterface {
public:
static constexpr const char * const NAME = "TestName";
static constexpr const char * const VERSION = "1.2.3";
};
TEST_F(DependencyManagerTestSuite, ProvideInterfaceInfo) {
class TestComponent : public TestInterface {};
celix::dm::DependencyManager dm{ctx};
auto& cmp = dm.createComponent<TestComponent>();
cmp.createProvidedService<TestInterface>(TestInterface::NAME)
.setVersion(TestInterface::VERSION);
cmp.build();
EXPECT_EQ(cmp.getState(), celix::dm::ComponentState::TRACKING_OPTIONAL);
auto info = dm.getInfo();
EXPECT_EQ(info.components[0].interfacesInfo[0].serviceName, "TestName");
auto& props = info.components[0].interfacesInfo[0].properties;
const auto it = props.find(celix::SERVICE_VERSION);
ASSERT_TRUE(it != props.end());
EXPECT_EQ(it->second, "1.2.3");
}
TEST_F(DependencyManagerTestSuite, CreateInterfaceInfo) {
class TestComponent : public TestInterface {};
celix::dm::DependencyManager dm{ctx};
auto& cmp = dm.createComponent<TestComponent>();
cmp.addInterfaceWithName<TestInterface>(TestInterface::NAME, TestInterface::VERSION);
cmp.build();
EXPECT_EQ(cmp.getState(), celix::dm::ComponentState::TRACKING_OPTIONAL);
auto info = dm.getInfo();
EXPECT_EQ(info.components[0].interfacesInfo[0].serviceName, "TestName");
auto& props = info.components[0].interfacesInfo[0].properties;
const auto it = props.find(celix::SERVICE_VERSION);
ASSERT_TRUE(it != props.end());
EXPECT_EQ(it->second, "1.2.3");
}
TEST_F(DependencyManagerTestSuite, TestPrintInfo) {
celix::dm::DependencyManager dm{ctx};
auto& cmp = dm.createComponent<Cmp1>();
cmp.addInterface<TestService>();
cmp.createServiceDependency<TestInterface>(TestInterface::NAME);
cmp.build();
char* buf = nullptr;
size_t bufLen = 0;
FILE* testStream = open_memstream(&buf, &bufLen);
celix_dependencyManager_printInfo(celix_bundleContext_getDependencyManager(ctx), true, true, testStream);
fclose(testStream);
std::cout << buf << std::endl;
EXPECT_TRUE(strstr(buf, "Cmp1")); //cmp name
EXPECT_TRUE(strstr(buf, "TestService")); //provided service name
EXPECT_TRUE(strstr(buf, "TestName")); //service dependency name
free(buf);
buf = nullptr;
bufLen = 0;
testStream = open_memstream(&buf, &bufLen);
celix_dependencyManager_printInfo(celix_bundleContext_getDependencyManager(ctx), true, false, testStream);
fclose(testStream);
EXPECT_TRUE(strstr(buf, "Cmp1")); //cmp name
EXPECT_TRUE(strstr(buf, "TestService")); //provided service name
EXPECT_TRUE(strstr(buf, "TestName")); //service dependency name
free(buf);
buf = nullptr;
bufLen = 0;
testStream = open_memstream(&buf, &bufLen);
celix_dependencyManager_printInfo(celix_bundleContext_getDependencyManager(ctx), false, true, testStream);
fclose(testStream);
EXPECT_TRUE(strstr(buf, "Cmp1")); //cmp name
free(buf);
buf = nullptr;
bufLen = 0;
testStream = open_memstream(&buf, &bufLen);
celix_dependencyManager_printInfo(celix_bundleContext_getDependencyManager(ctx), false, false, testStream);
fclose(testStream);
EXPECT_TRUE(strstr(buf, "Cmp1")); //cmp name
free(buf);
buf = nullptr;
bufLen = 0;
testStream = open_memstream(&buf, &bufLen);
celix_dependencyManager_printInfoForBundle(celix_bundleContext_getDependencyManager(ctx), true, true, 0, testStream);
fclose(testStream);
EXPECT_TRUE(strstr(buf, "Cmp1")); //cmp name
free(buf);
//bundle does not exist -> empty print
celix_dependencyManager_printInfoForBundle(celix_bundleContext_getDependencyManager(ctx), true, true, 1 /*non existing*/, stdout);
std::stringstream ss{};
ss << dm;
EXPECT_TRUE(strstr(ss.str().c_str(), "Cmp1"));
ss = std::stringstream{};
ss << cmp;
EXPECT_TRUE(strstr(ss.str().c_str(), "Cmp1"));
}