| /* |
| * 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 "celix/FrameworkFactory.h" |
| #include "celix_bundle_context.h" |
| #include "celix_framework.h" |
| #include "celix_scheduled_event.h" |
| |
| class ScheduledEventTestSuite : public ::testing::Test { |
| public: |
| #if defined(__APPLE__) || defined(TESTING_ON_CI) |
| const int ALLOWED_ERROR_MARGIN_IN_MS = 2000; |
| #else |
| const int ALLOWED_ERROR_MARGIN_IN_MS = 1000; |
| #endif |
| |
| const double ALLOWED_PROCESSING_TIME_IN_SECONDS = 0.1; |
| |
| ScheduledEventTestSuite() { |
| fw = celix::createFramework({{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"}, |
| {CELIX_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS, |
| std::to_string(ALLOWED_PROCESSING_TIME_IN_SECONDS)}}); |
| } |
| std::shared_ptr<celix::Framework> fw{}; |
| /** |
| * Wait for the given predicate to become true or the given time has elapsed. |
| * @param predicate predicate to check. |
| * @param within maximum time to wait. |
| */ |
| template <typename Rep, typename Period> |
| void waitFor(const std::function<bool()>& predicate, std::chrono::duration<Rep, Period> within) { |
| auto start = std::chrono::steady_clock::now(); |
| while (!predicate()) { |
| std::this_thread::sleep_for(std::chrono::milliseconds{1}); |
| auto now = std::chrono::steady_clock::now(); |
| auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start); |
| if (elapsed > within) { |
| break; |
| } |
| } |
| } |
| }; |
| |
| TEST_F(ScheduledEventTestSuite, OnceShotEventTest) { |
| auto ctx = fw->getFrameworkBundleContext(); |
| |
| struct event_info { |
| std::atomic<int> count{0}; |
| std::atomic<bool> removed{false}; |
| }; |
| event_info info{}; |
| |
| auto callback = [](void* data) { |
| auto* info = static_cast<event_info*>(data); |
| info->count++; |
| }; |
| |
| auto removeCallback = [](void* data) { |
| auto* info = static_cast<event_info*>(data); |
| info->removed = true; |
| }; |
| |
| // When I create a scheduled event with a 0 delay and a 0 interval (one short, directly scheduled) |
| celix_scheduled_event_options_t opts{}; |
| opts.callbackData = &info; |
| opts.callback = callback; |
| opts.removeCallbackData = &info; |
| opts.removeCallback = removeCallback; |
| |
| // And I schedule the event |
| long eventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| EXPECT_GE(eventId, 0); |
| |
| // Then the count becomes 1 within the error margin |
| waitFor([&]() { return info.count.load() == 1; }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(1, info.count.load()); |
| |
| // And the event remove callback is called within the error margin |
| waitFor([&]() { return info.removed.load(); }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_TRUE(info.removed.load()); |
| |
| celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), eventId); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, ScheduledEventTest) { |
| auto ctx = fw->getFrameworkBundleContext(); |
| |
| struct event_info { |
| std::atomic<int> count{0}; |
| std::atomic<bool> removed{false}; |
| }; |
| event_info info{}; |
| |
| auto callback = [](void* data) { |
| auto* info = static_cast<event_info*>(data); |
| info->count++; |
| }; |
| |
| auto removeCallback = [](void* data) { |
| auto* info = static_cast<event_info*>(data); |
| info->removed = true; |
| }; |
| |
| // When I create a scheduled event with a 10ms delay and a 20 ms interval |
| celix_scheduled_event_options_t opts{}; |
| opts.name = "Scheduled event test"; |
| opts.initialDelayInSeconds = 0.01; |
| opts.intervalInSeconds = 0.02; |
| opts.callbackData = &info; |
| opts.callback = callback; |
| opts.removeCallbackData = &info; |
| opts.removeCallback = removeCallback; |
| |
| // And I schedule the event |
| long eventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| EXPECT_GE(eventId, 0); |
| |
| // Then count becomes 3 or more within the initial delay + 2 x internal and an allowed error margin (3x) |
| int allowedTimeInMs = 10 + (2 * 20) + (3 * ALLOWED_ERROR_MARGIN_IN_MS); |
| waitFor([&]() { return info.count.load() >= 3; }, std::chrono::milliseconds{allowedTimeInMs}); |
| EXPECT_GE(info.count.load(), 3); |
| |
| // And the event remove callback is not called |
| EXPECT_FALSE(info.removed.load()); |
| |
| // And when I remove the event |
| celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), eventId); |
| |
| // Then the event remove callback is called |
| EXPECT_TRUE(info.removed.load()); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, IgnoreNegativeScheduledIdsTest) { |
| // Scheduled event wakeup, remove functions will ignore negative ids |
| EXPECT_EQ(CELIX_SUCCESS, |
| celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), -1)); |
| EXPECT_EQ(CELIX_SUCCESS, |
| celix_bundleContext_removeScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), -1)); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, ManyScheduledEventTest) { |
| auto ctx = fw->getFrameworkBundleContext(); |
| |
| struct event_info { |
| std::atomic<int> count{0}; |
| }; |
| event_info info{}; |
| auto callback = [](void* data) { |
| auto* info = static_cast<event_info*>(data); |
| info->count++; |
| }; |
| |
| std::vector<long> eventIds{}; |
| |
| // When 1000 scheduled events are with a random interval between 1 and 59 ms |
| for (int i = 0; i < 100; ++i) { |
| // When I create a scheduled event with a 10ms delay and a 20 ms interval |
| celix_scheduled_event_options_t opts{}; |
| opts.name = "Scheduled event test"; |
| opts.intervalInSeconds = (i % 50) / 100.0; // note will also contain one-shot scheduled events |
| opts.callbackData = &info; |
| opts.callback = callback; |
| long eventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| EXPECT_GE(eventId, 0); |
| if (opts.intervalInSeconds > 0) { // not a one-shot event |
| eventIds.push_back(eventId); |
| } |
| } |
| |
| // And some time passes, to let some events be called |
| std::this_thread::sleep_for(std::chrono::milliseconds{10}); |
| |
| // Then the events can safely be removed |
| for (auto id : eventIds) { |
| celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), id); |
| } |
| EXPECT_GT(info.count, 0); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, AddWithoutRemoveScheduledEventTest) { |
| // When I create a scheduled event |
| auto ctx = fw->getFrameworkBundleContext(); |
| |
| auto callback = [](void* /*data*/) { fprintf(stdout, "Scheduled event called\n"); }; |
| celix_scheduled_event_options_t opts{}; |
| opts.name = "Un-removed scheduled event test"; |
| opts.intervalInSeconds = 0.02; |
| opts.callback = callback; |
| long scheduledEventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| EXPECT_GE(scheduledEventId, 0); |
| |
| // And I do not remove the event, but let the bundle framework stop |
| // Then I expect no memory leaks |
| } |
| |
| TEST_F(ScheduledEventTestSuite, AddWithoutRemoveOneShotEventTest) { |
| // When I create a one-shot scheduled event with a long initial delay |
| auto ctx = fw->getFrameworkBundleContext(); |
| |
| auto callback = [](void* /*data*/) { FAIL() << "Scheduled event called, but should not be called"; }; |
| celix_scheduled_event_options_t opts{}; |
| opts.name = "Un-removed one-shot scheduled event test"; |
| opts.initialDelayInSeconds = 100; |
| opts.callback = callback; |
| long scheduledEventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| EXPECT_GE(scheduledEventId, 0); |
| |
| // And I do let the one-shot event trigger, but let the bundle framework stop |
| // Then I expect no memory leaks |
| } |
| |
| TEST_F(ScheduledEventTestSuite, InvalidOptionsAndArgumentsTest) { |
| // When I create a scheduled event with an invalid options |
| auto ctx = fw->getFrameworkBundleContext(); |
| celix_scheduled_event_options_t opts{}; // no callback |
| long scheduledEventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| // Then I expect an error |
| EXPECT_LT(scheduledEventId, 0); |
| |
| // When I create a scheduled event with negative initial delay |
| opts.name = "Invalid scheduled event test"; |
| opts.initialDelayInSeconds = -1; |
| opts.callback = [](void* /*data*/) { FAIL() << "Scheduled event called, but should not be called"; }; |
| scheduledEventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| // Then I expect an error |
| EXPECT_LT(scheduledEventId, 0); |
| |
| // When I create a scheduled event with negative interval value |
| opts.initialDelayInSeconds = 0.1; |
| opts.intervalInSeconds = -1; |
| scheduledEventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts); |
| // Then I expect an error |
| EXPECT_LT(scheduledEventId, 0); |
| |
| // celix_scheduleEvent_release and celix_scheduledEvent_retain can be called with NULL |
| celix_scheduledEvent_release(nullptr); |
| celix_scheduledEvent_retain(nullptr); |
| |
| // celix_bundleContext_removeScheduledEvent can handle invalid eventIds |
| celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), -1); |
| celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), 404); |
| |
| // celix_framework_scheduledEvent with no callback should return -1 |
| scheduledEventId = celix_framework_scheduleEvent(ctx->getFramework()->getCFramework(), |
| CELIX_FRAMEWORK_BUNDLE_ID, |
| nullptr, |
| 0.0, |
| 0.0, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr); |
| EXPECT_EQ(scheduledEventId, -1); |
| |
| // celix_framework_scheduledEvent with an invalid bndId should return -1 |
| scheduledEventId = celix_framework_scheduleEvent( |
| ctx->getFramework()->getCFramework(), 404, nullptr, 0.0, 0.0, nullptr, [](void*) { /*nop*/ }, nullptr, nullptr); |
| EXPECT_EQ(scheduledEventId, -1); |
| |
| // celix_framework_waitForScheduledEvent with an invalid bndId should return CELIX_ILLEGAL_ARGUMENT |
| celix_status_t status = celix_framework_waitForScheduledEvent(ctx->getFramework()->getCFramework(), 404, 1); |
| EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, WakeUpEventTest) { |
| // Given a counter scheduled event with a long initial delay is added |
| std::atomic<int> count{0}; |
| celix_scheduled_event_options_t opts{}; |
| opts.name = "test wakeup"; |
| opts.initialDelayInSeconds = 0.05; |
| opts.intervalInSeconds = 0.05; |
| opts.callbackData = static_cast<void*>(&count); |
| opts.callback = [](void* countPtr) { |
| auto* count = static_cast<std::atomic<int>*>(countPtr); |
| count->fetch_add(1); |
| }; |
| long scheduledEventId = |
| celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); |
| ASSERT_NE(-1L, scheduledEventId); |
| EXPECT_EQ(0, count.load()); |
| |
| // When the scheduled event is woken up |
| celix_status_t status = celix_bundleContext_wakeupScheduledEvent( |
| fw->getFrameworkBundleContext()->getCBundleContext(), scheduledEventId); |
| |
| // Then the status is CELIX_SUCCESS |
| ASSERT_EQ(CELIX_SUCCESS, status); |
| |
| // And the count becomes 1 within the error margin |
| waitFor([&]() { return count.load() == 1; }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(1, count.load()); |
| |
| // And the count becomes 2 within the interval including the error margin |
| auto now = std::chrono::steady_clock::now(); |
| waitFor([&]() { return count.load() == 2; }, std::chrono::milliseconds{50 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| auto end = std::chrono::steady_clock::now(); |
| auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - now).count(); |
| EXPECT_EQ(2, count.load()); |
| EXPECT_NEAR(50, diff, ALLOWED_ERROR_MARGIN_IN_MS); |
| |
| // When waking up the scheduled event again |
| status = celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), |
| scheduledEventId); |
| |
| // Then the status is CELIX_SUCCESS |
| ASSERT_EQ(CELIX_SUCCESS, status); |
| |
| // Then the count becomes 3 within the error margin |
| waitFor([&]() { return count.load() == 3; }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(3, count.load()); |
| |
| celix_bundleContext_removeScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), scheduledEventId); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, WakeUpOneShotEventTest) { |
| // Given a counter scheduled event with a long initial delay is added |
| std::atomic<int> count{0}; |
| celix_scheduled_event_options_t opts{}; |
| opts.name = "test one-shot wakeup"; |
| opts.initialDelayInSeconds = 5; |
| opts.callbackData = static_cast<void*>(&count); |
| opts.callback = [](void* countPtr) { |
| auto* count = static_cast<std::atomic<int>*>(countPtr); |
| count->fetch_add(1); |
| }; |
| long scheduledEventId = |
| celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); |
| ASSERT_GE(scheduledEventId, 0); |
| EXPECT_EQ(0, count.load()); |
| |
| // When the scheduled event is woken up |
| celix_status_t status = celix_bundleContext_wakeupScheduledEvent( |
| fw->getFrameworkBundleContext()->getCBundleContext(), scheduledEventId); |
| |
| // Then the status is CELIX_SUCCESS |
| ASSERT_EQ(CELIX_SUCCESS, status); |
| |
| // And the count becomes 1 within the error margin |
| waitFor([&]() { return count.load() == 1; }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(1, count.load()); |
| |
| // When the scheduled event is woken up again |
| status = celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), |
| scheduledEventId); |
| |
| // Then the status is ILLEGAL_ARGUMENT, because the scheduled event is already woken up and a one-shot event |
| ASSERT_EQ(CELIX_ILLEGAL_ARGUMENT, status); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, CxxScheduledEventTest) { |
| // Given a count and a callback to increase the count |
| std::atomic<int> count{0}; |
| auto callback = [&count]() { count.fetch_add(1); }; |
| |
| std::atomic<bool> removed{false}; |
| auto removeCallback = [&removed]() { removed.store(true); }; |
| |
| // And a scheduled event with a initial delay and interval of 50ms |
| auto event = fw->getFrameworkBundleContext() |
| ->scheduledEvent() |
| .withName("test cxx") |
| .withInitialDelay(std::chrono::milliseconds{50}) |
| .withInterval(std::chrono::milliseconds{50}) |
| .withCallback(callback) |
| .withRemoveCallback(removeCallback) |
| .build(); |
| |
| // Then the count is not yet increased |
| ASSERT_EQ(0, count.load()); |
| |
| // And the count becomes 1 within the initial delay, including the error margin |
| auto start = std::chrono::steady_clock::now(); |
| waitFor([&]() { return count.load() == 1; }, std::chrono::milliseconds{50 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| auto end = std::chrono::steady_clock::now(); |
| EXPECT_NEAR( |
| 50, std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(), ALLOWED_ERROR_MARGIN_IN_MS); |
| EXPECT_EQ(1, count.load()); |
| |
| // When waking up the event |
| event.wakeup(); |
| |
| // Then the count is increased with the error margin |
| waitFor([&]() { return count.load() == 2; }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(2, count.load()); |
| |
| // And the remove callback is not yet called |
| EXPECT_FALSE(removed.load()); |
| |
| // When canceling the event |
| event.cancel(); |
| |
| // And waiting longer than the interval |
| std::this_thread::sleep_for(std::chrono::milliseconds{50 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| |
| // Then the count is not increased |
| EXPECT_EQ(2, count.load()); |
| |
| // And the remove callback is called within the error margin |
| waitFor([&]() { return removed.load(); }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_TRUE(removed.load()); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, CxxScheduledEventRAIITest) { |
| // Given a count and a callback to increase the count |
| std::atomic<int> count{0}; |
| auto callback = [&count]() { count.fetch_add(1); }; |
| |
| std::atomic<bool> removed{false}; |
| auto removeCallback = [&removed]() { removed.store(true); }; |
| |
| { |
| // And a scoped scheduled event with an initial delay and interval of 50ms |
| auto event = fw->getFrameworkBundleContext() |
| ->scheduledEvent() |
| .withName("test cxx") |
| .withInitialDelay(std::chrono::milliseconds{100}) |
| .withInterval(std::chrono::milliseconds{50}) |
| .withCallback(callback) |
| .withRemoveCallback(removeCallback) |
| .build(); |
| |
| // Then the count is not yet increased |
| ASSERT_EQ(0, count.load()); |
| } |
| // When the event goes out of scope |
| |
| // Then the event removed callback is called within the allowed error margin |
| waitFor([&]() { return removed.load(); }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_TRUE(removed.load()); |
| |
| // And the count is not increased |
| EXPECT_EQ(0, count.load()); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, CxxOneShotScheduledEventTest) { |
| // Given a count and a callback to increase the count |
| std::atomic<int> count{0}; |
| auto callback = [&count]() { count.fetch_add(1); }; |
| |
| std::atomic<bool> removed{false}; |
| auto removeCallback = [&removed]() { removed.store(true); }; |
| |
| // And a scheduled event with a initial delay of 50ms |
| auto event = fw->getFrameworkBundleContext() |
| ->scheduledEvent() |
| .withName("test cxx one-shot") |
| .withInitialDelay(std::chrono::milliseconds{50}) |
| .withCallback(callback) |
| .withRemoveCallback(removeCallback) |
| .build(); |
| |
| // Then the count is not yet increased |
| ASSERT_EQ(0, count.load()); |
| |
| // And the remove callback is not yet called |
| EXPECT_FALSE(removed.load()); |
| |
| // And count will be increased within the initial delay (including some error margin) |
| waitFor([&] { return count.load() == 1; }, std::chrono::milliseconds{50 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(1, count.load()); |
| |
| // And the remove callback is called shortly after the initial delay, within the error margin |
| waitFor([&] { return removed.load(); }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_TRUE(removed.load()); |
| |
| // When waking up the event with a wait time of 1s |
| event.wakeup(); |
| |
| // And waiting a bit |
| std::this_thread::sleep_for(std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| |
| // Then the count is not increased, because the event is a one-shot event |
| EXPECT_EQ(1, count.load()); |
| |
| // When the event goes out of scope, it does not leak |
| } |
| |
| TEST_F(ScheduledEventTestSuite, CxxOneShotScheduledEventRAIITest) { |
| // Given a count and a callback to increase the count |
| std::atomic<int> count{0}; |
| auto callback = [&count]() { count.fetch_add(1); }; |
| |
| // And a remove boolean and a remove callback to set the boolean |
| std::atomic<bool> removed{false}; |
| auto removeCallback = [&removed]() { removed.store(true); }; |
| |
| { |
| // And a scoped scheduled event with an initial delay of 50ms |
| auto event = fw->getFrameworkBundleContext() |
| ->scheduledEvent() |
| .withName("test cxx one-shot") |
| .withInitialDelay(std::chrono::milliseconds{50}) |
| .withCallback(callback) |
| .withRemoveCallback(removeCallback) |
| .build(); |
| |
| // Then the count is not yet increased |
| ASSERT_EQ(0, count.load()); |
| |
| // And the remove callback is not yet called |
| EXPECT_FALSE(removed.load()); |
| } |
| // When the event is out of scope |
| |
| // Then the remove callback is not yet called, because a one-shot event is not canceled when it goes out of scope |
| EXPECT_FALSE(removed.load()); |
| |
| // And count will be increased within the initial delay (including some error margin) |
| waitFor([&] { return count.load() == 1; }, std::chrono::milliseconds{50 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(1, count.load()); |
| |
| // And the remove callback is called shortly after the initial delay |
| waitFor([&] { return removed.load(); }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_TRUE(removed.load()); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, CxxCreateScheduledEventWithNoCallbackTest) { |
| // When a scheduled event is created without a callback an exception is thrown |
| EXPECT_ANY_THROW(fw->getFrameworkBundleContext()->scheduledEvent().build()); // Note no callback |
| } |
| |
| TEST_F(ScheduledEventTestSuite, CxxCancelOneShotEventBeforeFiredTest) { |
| auto callback = []() { FAIL() << "Should not be called"; }; |
| |
| // Given a one shot scheduled event with an initial delay of 1s |
| auto event = fw->getFrameworkBundleContext() |
| ->scheduledEvent() |
| .withInitialDelay(std::chrono::seconds{1}) |
| .withCallback(callback) |
| .build(); |
| |
| // When the event is cancelled before the initial delay |
| event.cancel(); |
| |
| // And waiting a bit |
| std::this_thread::sleep_for(std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| |
| // Then the event is not fired and does not leak |
| } |
| |
| TEST_F(ScheduledEventTestSuite, RemoveScheduledEventAsyncTest) { |
| std::atomic<int> count{0}; |
| auto callback = [](void* data) { |
| auto* count = static_cast<std::atomic<int>*>(data); |
| count->fetch_add(1); |
| }; |
| |
| // Given a scheduled event with am initial delay of 1ms |
| celix_scheduled_event_options_t opts{}; |
| opts.initialDelayInSeconds = 0.01; |
| opts.callbackData = &count; |
| opts.callback = callback; |
| long eventId = celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); |
| EXPECT_GE(eventId, 0); |
| |
| // When the event is removed async |
| celix_bundleContext_removeScheduledEventAsync(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); |
| |
| // And waiting longer than the initial delay, including some error margin |
| std::this_thread::sleep_for(std::chrono::milliseconds{10 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| |
| // Then the event is not fired |
| EXPECT_EQ(0, count.load()); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, WaitForScheduledEventTest) { |
| std::atomic<int> count{0}; |
| auto callback = [](void* data) { |
| auto* count = static_cast<std::atomic<int>*>(data); |
| count->fetch_add(1); |
| }; |
| |
| // Given a scheduled event with an initial delay of 10ms and an interval of 10ms |
| celix_scheduled_event_options_t opts{}; |
| opts.initialDelayInSeconds = 0.01; |
| opts.intervalInSeconds = 0.01; |
| opts.callbackData = &count; |
| opts.callback = callback; |
| long eventId = celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); |
| EXPECT_GE(eventId, 0); |
| |
| // When waiting for the event with a timeout longer than the initial delay |
| auto status = |
| celix_bundleContext_waitForScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 2); |
| |
| // Then the return status is success |
| EXPECT_EQ(CELIX_SUCCESS, status) << "Unexpected status" << celix_strerror(status) << std::endl; |
| |
| // And the event is fired |
| EXPECT_EQ(1, count.load()); |
| |
| // When waiting for the event with a timeout longer than the interval |
| status = |
| celix_bundleContext_waitForScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 2); |
| |
| // Then the return status is success |
| EXPECT_EQ(CELIX_SUCCESS, status); |
| |
| // And the event is fired again |
| EXPECT_EQ(2, count.load()); |
| |
| celix_bundleContext_removeScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, WaitTooShortForScheduledEventTest) { |
| std::atomic<int> count{0}; |
| auto callback = [](void *data) { |
| auto *count = static_cast<std::atomic<int> *>(data); |
| count->fetch_add(1); |
| }; |
| |
| // Given a scheduled event with an initial delay of 1s and an interval of 1s |
| celix_scheduled_event_options_t opts{}; |
| opts.initialDelayInSeconds = 1; |
| opts.intervalInSeconds = 1; |
| opts.callbackData = &count; |
| opts.callback = callback; |
| long eventId = celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); |
| EXPECT_GE(eventId, 0); |
| |
| // When waiting too short for the event |
| celix_status_t status = celix_bundleContext_waitForScheduledEvent( |
| fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 0.0001); |
| |
| // Then the return status is timeout |
| EXPECT_EQ(ETIMEDOUT, status); |
| |
| // And th event is not fired |
| EXPECT_EQ(0, count.load()); |
| |
| // When event is woken up |
| status = celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); |
| |
| // Then the return status is success |
| EXPECT_EQ(CELIX_SUCCESS, status); |
| |
| // And the event will be fired |
| waitFor([&count]() { return count.load() == 1; }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); |
| EXPECT_EQ(1, count.load()); |
| |
| // When waiting too short for the next (interval based) event |
| status = celix_bundleContext_waitForScheduledEvent( |
| fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 0.0001); |
| |
| // Then the return status is timeout |
| EXPECT_EQ(ETIMEDOUT, status); |
| |
| celix_bundleContext_removeScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); |
| } |
| |
| TEST_F(ScheduledEventTestSuite, CxxWaitForScheduledEventTest) { |
| std::atomic<int> count{0}; |
| auto callback = [&count]() { count.fetch_add(1); }; |
| |
| // Given a scheduled event with an initial delay of 1ms and an interval of 1ms |
| auto event = fw->getFrameworkBundleContext() |
| ->scheduledEvent() |
| .withInitialDelay(std::chrono::milliseconds{10}) |
| .withInterval(std::chrono::milliseconds{10}) |
| .withCallback(callback) |
| .build(); |
| |
| // When waiting for the event with a timeout longer than the initial delay |
| auto success = event.waitFor(std::chrono::milliseconds{10 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| |
| // Then the return status is success |
| EXPECT_TRUE(success); |
| |
| // And the event is fired |
| EXPECT_EQ(1, count.load()); |
| |
| // When waiting to short for the event |
| success = event.waitFor(std::chrono::microseconds{1}); |
| |
| // Then the return status is false (timeout) |
| EXPECT_FALSE(success); |
| |
| // When waiting for the event with a timeout longer than the interval |
| success = event.waitFor(std::chrono::milliseconds{10 + ALLOWED_ERROR_MARGIN_IN_MS}); |
| |
| // Then the return status is success |
| EXPECT_TRUE(success); |
| |
| // And the event is fired again |
| EXPECT_EQ(2, count.load()); |
| } |
| |
| #ifndef __APPLE__ |
| TEST_F(ScheduledEventTestSuite, ScheduledEventTimeoutLogTest) { |
| //Disabled for __APPLE__, because the expected timeout in celix_scheduledEvent_waitForRemoved does not always |
| //trigger. see also ticket #587. |
| |
| //Given a framework with a log callback that counts the number of warning log messages |
| std::atomic<int> logCount{0}; |
| auto logCallback = [](void* handle, celix_log_level_e level, const char*, const char*, int, const char* format, va_list args){ |
| std::atomic<int>& count = *static_cast<std::atomic<int>*>(handle); |
| if (level == CELIX_LOG_LEVEL_WARNING) { |
| count.fetch_add(1); |
| } |
| FILE* output = stdout; |
| if (level == CELIX_LOG_LEVEL_FATAL || level == CELIX_LOG_LEVEL_ERROR || level == CELIX_LOG_LEVEL_WARNING) { |
| output = stderr; |
| } |
| fprintf(output, "%s: ", celix_logLevel_toString(level)); |
| vfprintf(output, format, args); |
| fprintf(output, "\n"); |
| }; |
| celix_framework_setLogCallback(fw->getCFramework(), &logCount, logCallback); |
| |
| |
| //And a scheduled event with an initial delay of 1ms and an interval of 1ms that waits for 200ms in the callback |
| //and remove callback. |
| |
| auto busyWaitCallback = [](void*){ |
| auto start = std::chrono::steady_clock::now(); |
| while (std::chrono::steady_clock::now() - start < std::chrono::milliseconds{200}) { |
| //busy wait |
| } |
| }; |
| |
| celix_scheduled_event_options_t opts{}; |
| opts.name = "Sleep while Processing and Removing Scheduled Event"; |
| opts.initialDelayInSeconds = 0.01; |
| opts.intervalInSeconds = 0.01; |
| opts.callback = busyWaitCallback; |
| opts.removeCallback = busyWaitCallback; |
| long eventId = celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); |
| |
| //When the event is woken up |
| celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); |
| |
| // Then the log callback is called with a warning log message within an error margin and processing sleep time, |
| // because callback took too long. |
| waitFor([&] { return logCount.load() == 1; }, |
| std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS + 200}); |
| EXPECT_EQ(1, logCount.load()); |
| |
| //When removing the event |
| celix_bundleContext_removeScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); |
| |
| //Then the log callback is called at least one more time with a warning log message, because remove |
| //callback took too long |
| //(note the logCount can be increased more than once, due to another processing thread) |
| EXPECT_GE(logCount.load(), 2); |
| } |
| #endif |
| |
| TEST_F(ScheduledEventTestSuite, ScheduledEventForInvactiveFramework) { |
| // Given a framework that is stopped |
| celix_framework_stopBundle(fw->getCFramework(), CELIX_FRAMEWORK_BUNDLE_ID); |
| celix_framework_waitForStop(fw->getCFramework()); |
| // When a scheduled event is added |
| std::atomic<int> count{0}; |
| auto callback = [](void* data) { |
| auto* count = static_cast<std::atomic<int>*>(data); |
| count->fetch_add(1); |
| }; |
| |
| celix_scheduled_event_options_t opts{}; |
| opts.initialDelayInSeconds = 0.01; |
| opts.callbackData = &count; |
| opts.callback = callback; |
| long eventId = celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); |
| EXPECT_LT(eventId, 0); |
| |
| // Then the event is not added |
| EXPECT_EQ(0, count.load()); |
| } |