blob: b19fbe969b640ba0fd933a08da6490406802fa70 [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 <thread>
#include <chrono>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <string.h>
#include <future>
#include "celix_api.h"
#include "celix_framework_factory.h"
#include "celix_service_factory.h"
#include "service_tracker_private.h"
class CelixBundleContextServicesTests : public ::testing::Test {
public:
celix_framework_t* fw = nullptr;
celix_bundle_context_t *ctx = nullptr;
celix_properties_t *properties = nullptr;
CelixBundleContextServicesTests() {
properties = properties_create();
properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true");
properties_set(properties, "org.osgi.framework.storage.clean", "onFirstInit");
properties_set(properties, "org.osgi.framework.storage", ".cacheBundleContextTestFramework");
properties_set(properties, "CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace");
fw = celix_frameworkFactory_createFramework(properties);
ctx = framework_getContext(fw);
}
~CelixBundleContextServicesTests() override {
celix_frameworkFactory_destroyFramework(fw);
}
CelixBundleContextServicesTests(CelixBundleContextServicesTests&&) = delete;
CelixBundleContextServicesTests(const CelixBundleContextServicesTests&) = delete;
CelixBundleContextServicesTests& operator=(CelixBundleContextServicesTests&&) = delete;
CelixBundleContextServicesTests& operator=(const CelixBundleContextServicesTests&) = delete;
};
TEST_F(CelixBundleContextServicesTests, registerService) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
ASSERT_TRUE(svcId >= 0);
celix_bundleContext_unregisterService(ctx, svcId);
};
TEST_F(CelixBundleContextServicesTests, registerServiceAsync) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
long svcId = celix_bundleContext_registerServiceAsync(ctx, &svc, calcName, nullptr);
ASSERT_TRUE(svcId >= 0);
celix_bundleContext_waitForAsyncRegistration(ctx, svcId);
ASSERT_GE(celix_bundleContext_findService(ctx, calcName), 0L);
celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL);
celix_bundleContext_waitForAsyncUnregistration(ctx, svcId);
ASSERT_LT(celix_bundleContext_findService(ctx, calcName), 0L);
};
TEST_F(CelixBundleContextServicesTests, incorrectUnregisterCalls) {
celix_bundleContext_unregisterService(ctx, 1);
celix_bundleContext_unregisterService(ctx, 2);
celix_bundleContext_unregisterService(ctx, -1);
celix_bundleContext_unregisterService(ctx, -2);
};
TEST_F(CelixBundleContextServicesTests, incorrectAsyncUnregisterCalls) {
celix_bundleContext_unregisterServiceAsync(ctx, 1, NULL, NULL);
celix_bundleContext_unregisterServiceAsync(ctx, 2, NULL, NULL);
celix_bundleContext_unregisterServiceAsync(ctx, -1, NULL, NULL);
celix_bundleContext_unregisterServiceAsync(ctx, -2, NULL, NULL);
};
TEST_F(CelixBundleContextServicesTests, registerMultipleAndUseServices) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
long svcId1 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
EXPECT_TRUE(svcId1 >= 0);
long svcId2 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
EXPECT_TRUE(svcId2 >= 0);
long svcId3 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
EXPECT_TRUE(svcId3 >= 0);
auto use = [](void *handle, void *svc) {
EXPECT_TRUE(svc != nullptr);
int *total = static_cast<int*>(handle);
struct calc *calc = static_cast<struct calc*>(svc);
int tmp = calc->calc(1);
*total += tmp;
};
int total = 0;
auto count = celix_bundleContext_useServices(ctx, "calc", &total, use);
EXPECT_EQ(3, count);
EXPECT_EQ(42 * 3, total);
celix_bundleContext_unregisterService(ctx, svcId3);
total = 0;
count = celix_bundleContext_useServices(ctx, "calc", &total, use);
EXPECT_EQ(2, count);
EXPECT_EQ(42 * 2, total);
total = 0;
bool called = celix_bundleContext_useService(ctx, "calc", &total, use);
EXPECT_TRUE(called);
EXPECT_EQ(42, total);
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId2);
//NOTE superfluous (note prints errors)
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId2);
};
TEST_F(CelixBundleContextServicesTests, registerAndUseService) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
ASSERT_TRUE(svcId >= 0);
int result = 0;
bool called = celix_bundleContext_useServiceWithId(ctx, svcId, calcName, &result, [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
int *result = static_cast<int*>(handle);
struct calc *calc = static_cast<struct calc*>(svc);
int tmp = calc->calc(2);
*result = tmp;
});
ASSERT_TRUE(called);
ASSERT_EQ(84, result);
result = 0;
long nonExistingSvcId = 101;
called = celix_bundleContext_useServiceWithId(ctx, nonExistingSvcId, calcName, &result, [](void *handle, void *svc) {
int *result = static_cast<int*>(handle);
struct calc *calc = static_cast<struct calc*>(svc);
int tmp = calc->calc(2);
*result = tmp;
});
ASSERT_TRUE(!called);
ASSERT_EQ(0, result); //e.g. not called
celix_bundleContext_unregisterService(ctx, svcId);
};
TEST_F(CelixBundleContextServicesTests, registerAndUseServiceWithTimeout) {
const int NR_ITERATIONS = 5; //NOTE this test is sensitive for triggering race condition in the celix framework, therefore is used a few times.
for (int i = 0; i < NR_ITERATIONS; ++i) {
printf("Iter %i\n", i);
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
celix_service_use_options_t opts{};
opts.filter.serviceName = "calc";
bool called = celix_bundleContext_useServiceWithOptions(ctx, &opts);
EXPECT_FALSE(called); //service not avail.
std::future<bool> result{std::async([&] {
opts.waitTimeoutInSeconds = 2.0;
//printf("Trying to call calc with timeout of %f\n", opts.waitTimeoutInSeconds);
bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &opts);
//printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
return calledAsync;
})};
long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
EXPECT_TRUE(svcId >= 0);
EXPECT_TRUE(result.get()); //should return true after waiting for the registered service.
celix_bundleContext_unregisterService(ctx, svcId);
//check if timeout is triggered
std::future<bool> result2{std::async([&] {
opts.waitTimeoutInSeconds = 0.1;
//printf("Trying to call calc with timeout of %f\n", opts.waitTimeoutInSeconds);
bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &opts);
//printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
return calledAsync;
})};
EXPECT_FALSE(result2.get()); //note service is away, so even with a wait the service is not found.
}
}
TEST_F(CelixBundleContextServicesTests, registerAsyncAndUseServiceWithTimeout) {
const int NR_ITERATIONS = 5; //NOTE this test is sensitive for triggering race condition in the celix framework, therefore is used a few times.
for (int i = 0; i < NR_ITERATIONS; ++i) {
printf("Iter %i\n", i);
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
celix_service_use_options_t opts{};
opts.filter.serviceName = "calc";
bool called = celix_bundleContext_useServiceWithOptions(ctx, &opts);
EXPECT_FALSE(called); //service not avail.
std::future<bool> result{std::async([&] {
opts.waitTimeoutInSeconds = 2.0;
//printf("Trying to call calc with timeout of %f\n", opts.waitTimeoutInSeconds);
bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &opts);
//printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
return calledAsync;
})};
long svcId = celix_bundleContext_registerServiceAsync(ctx, &svc, calcName, nullptr);
EXPECT_TRUE(svcId >= 0);
EXPECT_TRUE(result.get()); //should return true after waiting for the registered service.
celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL);
celix_bundleContext_waitForAsyncUnregistration(ctx, svcId);
//check if timeout is triggered
std::future<bool> result2{std::async([&] {
opts.waitTimeoutInSeconds = 0.1;
//printf("Trying to call calc with timeout of %f\n", opts.waitTimeoutInSeconds);
bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &opts);
//printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
return calledAsync;
})};
EXPECT_FALSE(result2.get()); //note service is away, so even with a wait the service is not found.
}
}
TEST_F(CelixBundleContextServicesTests, registerAndUseServiceWithCorrectVersion) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
celix_service_use_options_t use_opts{};
use_opts.filter.serviceName = "calc";
use_opts.filter.versionRange = "[1,2)";
celix_service_registration_options_t reg_opts{};
reg_opts.serviceName = calcName;
reg_opts.serviceVersion = "1.5";
reg_opts.svc = &svc;
bool called = celix_bundleContext_useServiceWithOptions(ctx, &use_opts);
ASSERT_TRUE(!called); //service not avail.
std::future<bool> result{std::async([&]{
use_opts.waitTimeoutInSeconds = 5.0;
printf("Trying to call calc with timeout of %f\n", use_opts.waitTimeoutInSeconds);
bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &use_opts);
printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
return calledAsync;
})};
long svcId = celix_bundleContext_registerServiceWithOptions(ctx, &reg_opts);
ASSERT_TRUE(svcId >= 0);
ASSERT_TRUE(result.get()); //should return true after waiting for the registered service.
celix_bundleContext_unregisterService(ctx, svcId);
}
TEST_F(CelixBundleContextServicesTests, registerAndUseServiceWithIncorrectVersion) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
celix_service_use_options_t use_opts{};
use_opts.filter.serviceName = "calc";
use_opts.filter.versionRange = "[2,3)";
celix_service_registration_options_t reg_opts{};
reg_opts.serviceName = calcName;
reg_opts.serviceVersion = "1.5";
reg_opts.svc = &svc;
bool called = celix_bundleContext_useServiceWithOptions(ctx, &use_opts);
ASSERT_TRUE(!called); //service not avail.
std::future<bool> result{std::async([&]{
use_opts.waitTimeoutInSeconds = 1.0;
printf("Trying to call calc with timeout of %f\n", use_opts.waitTimeoutInSeconds);
bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &use_opts);
printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
return calledAsync;
})};
long svcId = celix_bundleContext_registerServiceWithOptions(ctx, &reg_opts);
ASSERT_TRUE(svcId >= 0);
ASSERT_TRUE(!result.get());
celix_bundleContext_unregisterService(ctx, svcId);
}
TEST_F(CelixBundleContextServicesTests, registerAndUseWithForcedRaceCondition) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
ASSERT_TRUE(svcId >= 0);
struct sync {
std::mutex mutex{};
std::condition_variable sync{};
bool inUseCall{false};
bool readyToExitUseCall{false};
bool unregister{false};
int result{0};
};
struct sync callInfo{};
auto use = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
struct sync *h = static_cast<struct sync*>(handle);
std::cout << "setting isUseCall to true and syncing on readyToExitUseCall" << std::endl;
std::unique_lock<std::mutex> lock(h->mutex);
h->inUseCall = true;
h->sync.notify_all();
h->sync.wait(lock, [h]{return h->readyToExitUseCall;});
lock.unlock();
std::cout << "Calling calc " << std::endl;
struct calc *calc = static_cast<struct calc *>(svc);
int tmp = calc->calc(2);
h->result = tmp;
};
auto call = [&] {
bool called = celix_bundleContext_useServiceWithId(ctx, svcId, calcName, &callInfo, use);
ASSERT_TRUE(called);
ASSERT_EQ(84, callInfo.result);
};
std::thread useThread{call};
std::thread unregisterThread{[&]{
std::cout << "syncing to wait if use function is called ..." << std::endl;
std::unique_lock<std::mutex> lock(callInfo.mutex);
callInfo.sync.wait(lock, [&]{return callInfo.inUseCall;});
lock.unlock();
std::cout << "trying to unregister ..." << std::endl;
celix_bundleContext_unregisterService(ctx, svcId);
std::cout << "done unregistering" << std::endl;
}};
//sleep 100 milli to give unregister a change to sink in
std::cout << "before sleep" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "after sleep" << std::endl;
std::cout << "setting readyToExitUseCall and notify" << std::endl;
std::unique_lock<std::mutex> lock(callInfo.mutex);
callInfo.readyToExitUseCall = true;
lock.unlock();
callInfo.sync.notify_all();
useThread.join();
std::cout << "use thread joined" << std::endl;
unregisterThread.join();
std::cout << "unregister thread joined" << std::endl;
};
TEST_F(CelixBundleContextServicesTests, servicesTrackerTest) {
int count = 0;
auto add = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
int *c = static_cast<int*>(handle);
*c += 1;
};
auto remove = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
int *c = static_cast<int*>(handle);
*c -= 1;
};
long trackerId = celix_bundleContext_trackServices(ctx, "calc", &count, add, remove);
ASSERT_TRUE(trackerId >= 0);
ASSERT_EQ(0, count);
long svcId1 = celix_bundleContext_registerService(ctx, (void*)0x100, "calc", nullptr);
ASSERT_TRUE(svcId1 >= 0);
ASSERT_EQ(1, count);
long svcId2 = celix_bundleContext_registerService(ctx, (void*)0x200, "calc", nullptr);
ASSERT_TRUE(svcId2 >= 0);
ASSERT_EQ(2, count);
celix_bundleContext_unregisterService(ctx, svcId1);
ASSERT_EQ(1, count);
celix_bundleContext_stopTracker(ctx, trackerId);
celix_bundleContext_unregisterService(ctx, svcId2);
}
TEST_F(CelixBundleContextServicesTests, servicesTrackerTestAsync) {
std::atomic<int> count {0};
auto add = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
auto *c = static_cast<std::atomic<int>*>(handle);
*c += 1;
};
auto remove = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
auto *c = static_cast<std::atomic<int>*>(handle);
*c -= 1;
};
long trackerId = celix_bundleContext_trackServicesAsync(ctx, "calc", &count, add, remove);
ASSERT_TRUE(trackerId >= 0);
ASSERT_EQ(0, count);
long svcId1 = celix_bundleContext_registerServiceAsync(ctx, (void*)0x100, "calc", nullptr);
celix_bundleContext_waitForAsyncRegistration(ctx, svcId1); //note also means the service tracker is done
ASSERT_TRUE(svcId1 >= 0);
ASSERT_EQ(1, count);
long svcId2 = celix_bundleContext_registerServiceAsync(ctx, (void*)0x200, "calc", nullptr);
celix_bundleContext_waitForAsyncRegistration(ctx, svcId2);
ASSERT_TRUE(svcId2 >= 0);
ASSERT_EQ(2, count);
celix_bundleContext_unregisterServiceAsync(ctx, svcId1, NULL, NULL);
celix_bundleContext_waitForAsyncUnregistration(ctx, svcId1);
ASSERT_EQ(1, count);
celix_bundleContext_stopTrackerAsync(ctx, trackerId, NULL, NULL);
celix_bundleContext_unregisterServiceAsync(ctx, svcId2, NULL, NULL);
celix_framework_waitForEmptyEventQueue(fw);
}
TEST_F(CelixBundleContextServicesTests, servicesTrackerInvalidArgsTest) {
long trackerId = celix_bundleContext_trackServices(nullptr, nullptr, nullptr, nullptr, nullptr);
ASSERT_TRUE(trackerId < 0); //required ctx missing
trackerId = celix_bundleContext_trackServices(ctx, "calc", nullptr, nullptr, nullptr);
ASSERT_TRUE(trackerId >= 0); //valid
celix_bundleContext_stopTracker(ctx, trackerId);
//opts
trackerId = celix_bundleContext_trackServicesWithOptions(nullptr, nullptr);
ASSERT_TRUE(trackerId < 0); //required ctx and opts missing
trackerId = celix_bundleContext_trackServicesWithOptions(ctx, nullptr);
ASSERT_TRUE(trackerId < 0); //required opts missing
celix_service_tracking_options_t opts{};
trackerId = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
ASSERT_TRUE(trackerId >= 0); //valid with empty opts
celix_bundleContext_stopTracker(ctx, trackerId);
opts.filter.serviceName = "calc";
trackerId = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
ASSERT_TRUE(trackerId >= 0); //valid
celix_bundleContext_stopTracker(ctx, trackerId);
}
TEST_F(CelixBundleContextServicesTests, servicesTrackerTestWithAlreadyRegisteredServices) {
int count = 0;
auto add = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
int *c = static_cast<int*>(handle);
*c += 1;
};
auto remove = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
int *c = static_cast<int*>(handle);
*c -= 1;
};
long svcId1 = celix_bundleContext_registerService(ctx, (void*)0x010, "calc", nullptr);
long svcId2 = celix_bundleContext_registerService(ctx, (void*)0x020, "calc", nullptr);
long trackerId = celix_bundleContext_trackServices(ctx, "calc", &count, add, remove);
ASSERT_TRUE(trackerId >= 0);
ASSERT_EQ(2, count);
long svcId3 = celix_bundleContext_registerService(ctx, (void*)0x100, "calc", nullptr);
ASSERT_TRUE(svcId1 >= 0);
ASSERT_EQ(3, count);
long svcId4 = celix_bundleContext_registerService(ctx, (void*)0x200, "calc", nullptr);
ASSERT_TRUE(svcId2 >= 0);
ASSERT_EQ(4, count);
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId3);
ASSERT_EQ(2, count);
celix_bundleContext_stopTracker(ctx, trackerId);
celix_bundleContext_unregisterService(ctx, svcId2);
celix_bundleContext_unregisterService(ctx, svcId4);
}
TEST_F(CelixBundleContextServicesTests, servicesTrackerTestWithProperties) {
int count = 0;
auto add = [](void *handle, void *svc, const properties_t *props) {
ASSERT_TRUE(svc != nullptr);
ASSERT_STRCASEEQ("C", celix_properties_get(props, CELIX_FRAMEWORK_SERVICE_LANGUAGE, nullptr));
int *c = static_cast<int*>(handle);
*c += 1;
};
auto remove = [](void *handle, void *svc, const properties_t *props) {
ASSERT_TRUE(svc != nullptr);
ASSERT_STRCASEEQ("C", celix_properties_get(props, CELIX_FRAMEWORK_SERVICE_LANGUAGE, nullptr));
int *c = static_cast<int*>(handle);
*c -= 1;
};
long svcId1 = celix_bundleContext_registerService(ctx, (void*)0x100, "calc", nullptr);
celix_service_tracking_options_t opts{};
opts.filter.serviceName = "calc";
opts.callbackHandle = &count;
opts.addWithProperties = add;
opts.removeWithProperties = remove;
long trackerId = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
ASSERT_TRUE(trackerId >= 0);
ASSERT_EQ(1, count);
long svcId2 = celix_bundleContext_registerService(ctx, (void*)0x200, "calc", nullptr);
ASSERT_TRUE(svcId1 >= 0);
ASSERT_EQ(2, count);
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId2);
ASSERT_EQ(0, count);
celix_bundleContext_stopTracker(ctx, trackerId);
}
TEST_F(CelixBundleContextServicesTests, servicesTrackerTestWithOwner) {
int count = 0;
auto add = [](void *handle, void *svc, const properties_t *props, const bundle_t *svcOwner) {
ASSERT_TRUE(svc != nullptr);
ASSERT_STRCASEEQ("C", celix_properties_get(props, CELIX_FRAMEWORK_SERVICE_LANGUAGE, nullptr));
ASSERT_TRUE(celix_bundle_getId(svcOwner) >= 0);
int *c = static_cast<int*>(handle);
*c += 1;
};
auto remove = [](void *handle, void *svc, const properties_t *props, const bundle_t *svcOwner) {
ASSERT_TRUE(svc != nullptr);
ASSERT_STRCASEEQ("C", celix_properties_get(props, CELIX_FRAMEWORK_SERVICE_LANGUAGE, nullptr));
ASSERT_TRUE(celix_bundle_getId(svcOwner) >= 0);
int *c = static_cast<int*>(handle);
*c -= 1;
};
long svcId1 = celix_bundleContext_registerService(ctx, (void*)0x100, "calc", nullptr);
celix_service_tracking_options_t opts{};
opts.filter.serviceName = "calc";
opts.callbackHandle = &count;
opts.addWithOwner = add;
opts.removeWithOwner = remove;
long trackerId = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
ASSERT_TRUE(trackerId >= 0);
ASSERT_EQ(1, count);
long svcId2 = celix_bundleContext_registerService(ctx, (void*)0x200, "calc", nullptr);
ASSERT_TRUE(svcId1 >= 0);
ASSERT_EQ(2, count);
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId2);
ASSERT_EQ(0, count);
celix_bundleContext_stopTracker(ctx, trackerId);
}
TEST_F(CelixBundleContextServicesTests, serviceTrackerWithRaceConditionTest) {
struct calc {
int (*calc)(int);
};
const char *calcName = "calc";
struct calc svc;
svc.calc = [](int n) -> int {
return n * 42;
};
struct data {
std::mutex mutex{};
std::condition_variable sync{};
long svcId {-1};
bool inAddCall{false};
bool inRemoveCall{false};
bool readyToExit{false};
int result{0};
};
struct data data{};
auto add = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
struct data *d = static_cast<struct data*>(handle);
std::unique_lock<std::mutex> lock(d->mutex);
d->inAddCall = true;
d->sync.notify_all();
d->sync.wait(lock, [d]{return d->readyToExit;});
lock.unlock();
struct calc *calc = static_cast<struct calc *>(svc);
int tmp = calc->calc(2);
lock.lock();
d->result = tmp;
lock.unlock();
};
auto remove = [](void *handle, void *svc) {
ASSERT_TRUE(svc != nullptr);
struct data *d = static_cast<struct data*>(handle);
std::unique_lock<std::mutex> lock(d->mutex);
std::cout << "In Remove call. waiting for ready to exit" << std::endl;
d->inRemoveCall = true;
d->sync.notify_all();
d->sync.wait(lock, [d]{return d->readyToExit;});
lock.unlock();
};
long trackerId = celix_bundleContext_trackServices(ctx, calcName, &data, add, remove);
std::thread registerThread{[&]{
long id = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
std::cout << "registered service with id " << id << std::endl;
std::lock_guard<std::mutex> lock{data.mutex};
data.svcId = id;
data.sync.notify_all();
}};
std::thread unregisterThread{[&]{
long id = -1;
std::unique_lock<std::mutex> lock(data.mutex);
data.sync.wait(lock, [&]{return data.svcId >= 0;});
id = data.svcId;
lock.unlock();
std::cout << "trying to unregister ... with id " << id << std::endl;
celix_bundleContext_unregisterService(ctx, id);
std::cout << "done unregistering" << std::endl;
}};
std::cout << "waiting till inAddCall" << std::endl;
std::unique_lock<std::mutex> lock{data.mutex};
data.sync.wait(lock, [&]{return data.inAddCall;});
data.readyToExit = true;
data.sync.notify_all();
lock.unlock();
//let unregister sink in.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
lock.lock();
std::cout << "triggering ready to exit" << std::endl;
data.readyToExit = true;
data.sync.notify_all();
lock.unlock();
registerThread.join();
unregisterThread.join();
std::cout << "threads joined" << std::endl;
ASSERT_EQ(84, data.result);
ASSERT_TRUE(data.inAddCall);
ASSERT_TRUE(data.inRemoveCall);
celix_bundleContext_stopTracker(ctx, trackerId);
};
TEST_F(CelixBundleContextServicesTests, servicesTrackerSetTest) {
int count = 0;
void *svc1 = (void*)0x100; //no ranking
void *svc2 = (void*)0x200; //no ranking
void *svc3 = (void*)0x300; //10 ranking
void *svc4 = (void*)0x400; //5 ranking
auto set = [](void *handle, void *svc) {
static int callCount = 0;
callCount += 1;
if (callCount == 1) {
//first time svc1 should be set (oldest service with equal ranking
ASSERT_EQ(0x100, (long)svc);
} else if (callCount == 2) {
ASSERT_EQ(0x300, (long)svc);
//second time svc3 should be set (highest ranking)
} else if (callCount == 3) {
//third time svc4 should be set (highest ranking
ASSERT_EQ(0x400, (long)svc);
}
int *c = static_cast<int*>(handle);
*c = callCount;
};
long svcId1 = celix_bundleContext_registerService(ctx, svc1, "NA", nullptr);
long svcId2 = celix_bundleContext_registerService(ctx, svc2, "NA", nullptr);
//starting tracker should lead to first set call
celix_service_tracking_options_t opts{};
opts.callbackHandle = (void*)&count;
opts.filter.serviceName = "NA";
opts.set = set;
long trackerId = celix_bundleContext_trackServicesWithOptions(ctx, &opts); //call 1
ASSERT_TRUE(trackerId >= 0);
//register svc3 should lead to second set call
properties_t *props3 = celix_properties_create();
celix_properties_set(props3, OSGI_FRAMEWORK_SERVICE_RANKING, "10");
long svcId3 = celix_bundleContext_registerService(ctx, svc3, "NA", props3); //call 2
//register svc4 should lead to no set (lower ranking)
properties_t *props4 = celix_properties_create();
celix_properties_set(props4, OSGI_FRAMEWORK_SERVICE_RANKING, "10");
long svcId4 = celix_bundleContext_registerService(ctx, svc4, "NA", props4); //no update
//unregister svc3 should lead to set (new highest ranking)
celix_bundleContext_unregisterService(ctx, svcId3); //call 3
celix_bundleContext_stopTracker(ctx, trackerId); //call 4 (NULL)
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId2);
celix_bundleContext_unregisterService(ctx, svcId4);
ASSERT_EQ(4, count); //check if the set is called the expected times
}
TEST_F(CelixBundleContextServicesTests, trackAllServices) {
std::atomic<size_t> count{0};
void *svc1 = (void *) 0x100; //no ranking
void *svc2 = (void *) 0x200; //no ranking
void *svc3 = (void *) 0x300; //10 ranking
void *svc4 = (void *) 0x400; //5 ranking
long svcId1 = celix_bundleContext_registerService(ctx, svc1, "svc_type1", nullptr);
long svcId2 = celix_bundleContext_registerService(ctx, svc2, "svc_type1", nullptr);
long svcId3 = celix_bundleContext_registerService(ctx, svc3, "svc_type2", nullptr);
long svcId4 = celix_bundleContext_registerService(ctx, svc4, "svc_type2", nullptr);
celix_service_tracking_options_t opts{};
opts.callbackHandle = (void *) &count;
opts.filter.serviceName = nullptr;
opts.callbackHandle = (void *) &count;
opts.add = [](void *handle, void *) {
auto c = (std::atomic<size_t> *) handle;
c->fetch_add(1);
};
long trackerId = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
EXPECT_GE(trackerId, 0);
EXPECT_EQ(4, count.load());
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId2);
celix_bundleContext_unregisterService(ctx, svcId3);
celix_bundleContext_unregisterService(ctx, svcId4);
celix_bundleContext_stopTracker(ctx, trackerId);
}
TEST_F(CelixBundleContextServicesTests, metaTrackAllServiceTrackers) {
std::atomic<size_t> count{0};
auto add = [](void *handle, const celix_service_tracker_info_t*) {
auto *c = (std::atomic<size_t>*)handle;
c->fetch_add(1);
};
long trkId1 = celix_bundleContext_trackServiceTrackers(ctx, nullptr, (void*)&count, add, nullptr);
EXPECT_TRUE(trkId1 >= 0);
celix_service_tracking_options_t opts{};
opts.filter.serviceName = "service1";
long trkId2 = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
EXPECT_TRUE(trkId2 >= 0);
opts.filter.serviceName = "service2";
long trkId3 = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
EXPECT_TRUE(trkId3 >= 0);
EXPECT_EQ(2, count.load());
celix_bundleContext_stopTracker(ctx, trkId1);
celix_bundleContext_stopTracker(ctx, trkId2);
celix_bundleContext_stopTracker(ctx, trkId3);
}
TEST_F(CelixBundleContextServicesTests, serviceFactoryTest) {
struct calc {
int (*calc)(int);
};
auto name = "CALC";
int count = 0;
celix_service_factory_t fac;
memset(&fac, 0, sizeof(fac));
fac.handle = (void*)&count;
fac.getService = [](void *handle, const celix_bundle_t *, const celix_properties_t *) -> void* {
auto *c = (int *)handle;
*c += 1;
static struct calc svc; //normally a service per bundle
svc.calc = [](int arg) { return arg * 42; };
return &svc;
};
fac.ungetService = [](void *handle, const celix_bundle_t *, const celix_properties_t *) {
auto *c = (int *)handle;
*c += 1;
};
long facId = celix_bundleContext_registerServiceFactory(ctx, &fac, name, nullptr);
ASSERT_TRUE(facId >= 0);
int result = -1;
bool called = celix_bundleContext_useService(ctx, name, &result, [](void *handle, void* svc) {
auto *r = (int *)(handle);
auto *calc = (struct calc*)svc;
*r = calc->calc(2);
});
ASSERT_TRUE(called);
ASSERT_EQ(84, result);
ASSERT_EQ(2, count); //expecting getService & unGetService to be called during the useService call.
celix_bundleContext_unregisterService(ctx, facId);
}
TEST_F(CelixBundleContextServicesTests, asyncServiceFactoryTest) {
struct calc {
int (*calc)(int);
};
auto name = "CALC";
int count = 0;
celix_service_factory_t fac;
memset(&fac, 0, sizeof(fac));
fac.handle = (void*)&count;
fac.getService = [](void *handle, const celix_bundle_t *, const celix_properties_t *) -> void* {
auto *c = (int *)handle;
*c += 1;
static struct calc svc; //normally a service per bundle
svc.calc = [](int arg) { return arg * 42; };
return &svc;
};
fac.ungetService = [](void *handle, const celix_bundle_t *, const celix_properties_t *) {
auto *c = (int *)handle;
*c += 1;
};
long facId = celix_bundleContext_registerServiceFactoryAsync(ctx, &fac, name, nullptr);
ASSERT_TRUE(facId >= 0);
celix_bundleContext_waitForAsyncRegistration(ctx, facId);
int result = -1;
bool called = celix_bundleContext_useService(ctx, name, &result, [](void *handle, void* svc) {
auto *r = (int *)(handle);
auto *calc = (struct calc*)svc;
*r = calc->calc(2);
});
ASSERT_TRUE(called);
ASSERT_EQ(84, result);
ASSERT_EQ(2, count); //expecting getService & unGetService to be called during the useService call.
celix_bundleContext_unregisterServiceAsync(ctx, facId, NULL, NULL);
}
TEST_F(CelixBundleContextServicesTests, findServicesTest) {
long svcId1 = celix_bundleContext_registerService(ctx, (void*)0x100, "example", nullptr);
long svcId2 = celix_bundleContext_registerService(ctx, (void*)0x100, "example", nullptr);
long svcId3 = celix_bundleContext_registerService(ctx, (void*)0x100, "example", nullptr);
long svcId4 = celix_bundleContext_registerService(ctx, (void*)0x100, "example", nullptr);
long foundId = celix_bundleContext_findService(ctx, "non existing service name");
ASSERT_EQ(-1L, foundId);
foundId = celix_bundleContext_findService(ctx, "example");
ASSERT_EQ(foundId, svcId1); //oldest should have highest ranking
array_list_t *list = celix_bundleContext_findServices(ctx, "non existing service name");
ASSERT_EQ(0, celix_arrayList_size(list));
arrayList_destroy(list);
list = celix_bundleContext_findServices(ctx, "example");
ASSERT_EQ(4, celix_arrayList_size(list));
arrayList_destroy(list);
celix_bundleContext_unregisterService(ctx, svcId1);
celix_bundleContext_unregisterService(ctx, svcId3);
celix_bundleContext_unregisterService(ctx, svcId4);
celix_service_filter_options_t opts{};
opts.serviceName = "example";
foundId = celix_bundleContext_findServiceWithOptions(ctx, &opts);
ASSERT_EQ(foundId, svcId2); //only one left
celix_bundleContext_unregisterService(ctx, svcId2);
}
TEST_F(CelixBundleContextServicesTests, trackServiceTrackerTest) {
int count = 0;
auto add = [](void *handle, const celix_service_tracker_info_t *info) {
EXPECT_STRCASEEQ("example", info->serviceName);
EXPECT_STRCASEEQ(CELIX_FRAMEWORK_SERVICE_C_LANGUAGE, info->serviceLanguage);
auto *c = static_cast<int*>(handle);
*c += 1;
};
auto remove = [](void *handle, const celix_service_tracker_info_t *info) {
EXPECT_STRCASEEQ("example", info->serviceName);
EXPECT_STRCASEEQ(CELIX_FRAMEWORK_SERVICE_C_LANGUAGE, info->serviceLanguage);
auto *c = static_cast<int*>(handle);
*c -= 1;
};
long trackerId = celix_bundleContext_trackServiceTrackers(ctx, "example", &count, add, remove);
EXPECT_TRUE(trackerId >= 0);
EXPECT_EQ(0, count);
long tracker2 = celix_bundleContext_trackService(ctx, "example", nullptr, nullptr);
EXPECT_TRUE(tracker2 >= 0);
EXPECT_EQ(1, count);
long tracker3 = celix_bundleContext_trackServices(ctx, "example", nullptr, nullptr, nullptr);
EXPECT_TRUE(tracker3 >= 0);
EXPECT_EQ(2, count);
long tracker4 = celix_bundleContext_trackServices(ctx, "no-match", nullptr, nullptr, nullptr);
EXPECT_TRUE(tracker4 >= 0);
EXPECT_EQ(2, count);
celix_bundleContext_stopTracker(ctx, tracker2);
EXPECT_EQ(1, count);
celix_bundleContext_stopTracker(ctx, tracker3);
EXPECT_EQ(0, count);
celix_bundleContext_stopTracker(ctx, trackerId);
celix_bundleContext_stopTracker(ctx, tracker4);
}
TEST_F(CelixBundleContextServicesTests, floodEventLoopTest) {
struct callback_data {
std::mutex mutex{};
std::condition_variable cond{};
bool ready{false};
};
callback_data data{};
//test so that the framework needs to use dynamic allocated event on the event loop
celix_service_registration_options_t opts{};
opts.svc = (void*)0x42;
opts.serviceName = "test";
opts.asyncData = (void*)&data;
opts.asyncCallback = [](void *d, long /*svcId*/) {
auto *localData = static_cast<callback_data*>(d);
std::unique_lock<std::mutex> lck{localData->mutex};
localData->cond.wait_for(lck, std::chrono::seconds{30}, [&]{ return localData->ready; }); //wait til ready.
EXPECT_TRUE(localData->ready);
};
long svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts);
EXPECT_GE(svcId, 0);
int nrOfAdditionalRegistrations = 300;
std::vector<long> svcIds{};
std::vector<long> trackerIds{};
for (int i = 0; i < nrOfAdditionalRegistrations; ++i) {
long id = celix_bundleContext_registerServiceAsync(ctx, (void*)0x42, "test", nullptr); //note cannot be completed because the first service registration in blocking in the event loop.
EXPECT_GE(id, 0);
svcIds.push_back(id);
trackerIds.push_back(celix_bundleContext_trackServicesAsync(ctx, "test", nullptr, nullptr, nullptr));
//CHECK if celix_bundleContext_isServiceRegistered work
EXPECT_FALSE(celix_bundleContext_isServiceRegistered(ctx, id));
}
{
//let the first service registration continue and as result all the following registrations.
std::lock_guard<std::mutex> lck{data.mutex};
data.ready = true;
data.cond.notify_all();
}
celix_bundleContext_waitForAsyncRegistration(ctx, svcId);
EXPECT_TRUE(celix_bundleContext_isServiceRegistered(ctx, svcId));
long foundId = celix_bundleContext_findService(ctx, "test");
EXPECT_GE(foundId, 0);
celix_bundleContext_unregisterServiceAsync(ctx, svcId, nullptr, nullptr);
for (auto id : svcIds) {
celix_bundleContext_unregisterServiceAsync(ctx, id, nullptr, nullptr);
celix_bundleContext_findService(ctx, "test"); //just to add some entropy
}
for (auto id : trackerIds) {
celix_bundleContext_stopTrackerAsync(ctx, id, nullptr, nullptr);
}
}
TEST_F(CelixBundleContextServicesTests, serviceOnDemandWithAsyncRegisterTest) {
//NOTE that even though service are registered async, they should be found by a useService call.
bool called = celix_bundleContext_useService(ctx, "test", nullptr, [](void*, void*){/*nop*/});
EXPECT_FALSE(called); //service not available
struct test_service {
void* handle;
};
struct callback_data {
celix_bundle_context_t* ctx;
long svcId;
test_service ts;
};
callback_data cbData{ctx, -1L, {nullptr}};
long trkId = celix_bundleContext_trackServiceTrackers(ctx, "test", &cbData, [](void *voidData, const celix_service_tracker_info_t*) {
auto* data = static_cast<callback_data*>(voidData);
data->svcId = celix_bundleContext_registerServiceAsync(data->ctx, &data->ts, "test", nullptr);
}, nullptr);
called = celix_bundleContext_useService(ctx, "test", nullptr, nullptr);
EXPECT_TRUE(called); //service created on demand.
celix_bundleContext_unregisterService(ctx, cbData.svcId);
celix_bundleContext_stopTracker(ctx, trkId);
}
TEST_F(CelixBundleContextServicesTests, startStopServiceTrackerAsync) {
std::atomic<int> count{0};
auto cb = [](void* data) {
auto* c = static_cast<std::atomic<int>*>(data);
(*c)++;
};
celix_service_tracking_options_t opts{};
opts.trackerCreatedCallbackData = &count;
opts.trackerCreatedCallback = cb;
long trkId = celix_bundleContext_trackServicesWithOptionsAsync(ctx, &opts);
EXPECT_GE(trkId, 0);
celix_bundleContext_waitForAsyncTracker(ctx, trkId);
EXPECT_EQ(count.load(), 1); //1x tracker started
celix_bundleContext_stopTrackerAsync(ctx, trkId, &count, cb);
celix_bundleContext_waitForAsyncStopTracker(ctx, trkId);
EXPECT_EQ(2, count.load()); //1x tracker started, 1x tracker stopped
}
TEST_F(CelixBundleContextServicesTests, startStopMetaServiceTrackerAsync) {
std::atomic<int> count{0};
auto cb = [](void* data) {
auto* c = static_cast<std::atomic<int>*>(data);
(*c)++;
};
long trkId = celix_bundleContext_trackServiceTrackersAsync(ctx, "test", nullptr, nullptr, nullptr, &count, cb);
EXPECT_GE(trkId, 0);
celix_bundleContext_waitForAsyncTracker(ctx, trkId);
EXPECT_EQ(count.load(), 1); //1x tracker started
celix_bundleContext_stopTrackerAsync(ctx, trkId, &count, cb);
celix_bundleContext_waitForAsyncStopTracker(ctx, trkId);
EXPECT_EQ(2, count.load()); //1x tracker started, 1x tracker stopped
}
TEST_F(CelixBundleContextServicesTests, onlyCallAsyncCallbackWithAsyncApi) {
celix_service_tracking_options_t opts{};
opts.trackerCreatedCallback = [](void *) {
FAIL();
};
long trkId = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
EXPECT_GT(trkId, 0);
celix_bundleContext_stopTracker(ctx, trkId);
celix_service_registration_options_t opts2{};
opts2.serviceName = "test";
opts2.svc = (void*)0x42;
opts2.asyncCallback = [](void*, long) {
FAIL();
};
long svcId = celix_bundleContext_registerServiceWithOptions(ctx, &opts2);
EXPECT_GT(svcId, 0);
celix_bundleContext_waitForEvents(ctx);
celix_bundleContext_unregisterService(ctx, trkId);
celix_bundle_tracking_options_t opts3{};
opts3.trackerCreatedCallback = [](void *) {
FAIL();
};
trkId = celix_bundleContext_trackBundlesWithOptions(ctx, &opts3);
EXPECT_GT(trkId, 0);
celix_bundleContext_stopTracker(ctx, trkId);
}