| |
| #include "bmc/scheduler_bmc.h" |
| |
| #include <chrono> // NOLINT(build/c++11) |
| #include <iostream> |
| #include <memory> |
| #include <vector> |
| |
| #include "scheduler_interface.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "boost/asio/io_context.hpp" // NOLINT(readability/boost) |
| |
| namespace safepower_agent { |
| namespace { |
| |
| TEST(Scheduler_fixtures, periodicCall) { |
| testing::MockFunction<void()> MockedCallBack; |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| constexpr absl::string_view task_name = "test_task"; |
| |
| EXPECT_EQ(scheduler->PeriodicCall(MockedCallBack.AsStdFunction(), |
| absl::Milliseconds(100), task_name), |
| absl::OkStatus()); |
| // wait .5 seconds |
| EXPECT_CALL(MockedCallBack, Call()).Times(testing::Between(4, 5)); |
| test_io->run_for(std::chrono::milliseconds(500)); |
| } |
| |
| TEST(Scheduler_fixtures, delayCall) { |
| testing::MockFunction<void()> MockedCallBack; |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| |
| constexpr absl::string_view task_name = "test_task_delayCall"; |
| EXPECT_EQ(scheduler->DelayCall(MockedCallBack.AsStdFunction(), |
| absl::Milliseconds(100), task_name), |
| absl::OkStatus()); |
| EXPECT_CALL(MockedCallBack, Call()).Times(1); |
| test_io->run_for(std::chrono::milliseconds(250)); |
| } |
| |
| TEST(Scheduler_fixtures, cancelCall) { |
| testing::MockFunction<void()> MockedCallBack_1; |
| testing::MockFunction<void()> MockedCallBack_2; |
| |
| EXPECT_CALL(MockedCallBack_1, Call()).Times(2); |
| EXPECT_CALL(MockedCallBack_2, Call()).Times(3); |
| |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| |
| constexpr absl::string_view task_name_1 = "test_task_1"; |
| constexpr absl::string_view task_name_2 = "test_task_2"; |
| ASSERT_EQ(scheduler->PeriodicCall(MockedCallBack_1.AsStdFunction(), |
| absl::Milliseconds(100), task_name_1), |
| absl::OkStatus()); |
| ASSERT_EQ(scheduler->PeriodicCall(MockedCallBack_2.AsStdFunction(), |
| absl::Milliseconds(150), task_name_2), |
| absl::OkStatus()); |
| |
| test_io->run_for(std::chrono::milliseconds(250)); |
| EXPECT_EQ(scheduler->CancelCall(task_name_1), absl::OkStatus()); |
| EXPECT_EQ(scheduler->PeriodicCall([] { /*do nothing*/ }, |
| absl::Milliseconds(100), task_name_1), |
| absl::OkStatus()); |
| EXPECT_NE(scheduler->PeriodicCall([] { /*do nothing*/ }, |
| absl::Milliseconds(150), task_name_2), |
| absl::OkStatus()); |
| test_io->run_for(std::chrono::milliseconds(250)); |
| EXPECT_EQ(scheduler->CancelCall(task_name_2), absl::OkStatus()); |
| EXPECT_EQ(scheduler->PeriodicCall([] { /*do nothing*/ }, |
| absl::Milliseconds(150), task_name_2), |
| absl::OkStatus()); |
| } |
| |
| TEST(Scheduler_fixtures, cancelAll) { |
| testing::MockFunction<void()> MockedCallBack; |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| |
| constexpr absl::string_view task_name_1 = "test_task_1"; |
| constexpr absl::string_view task_name_2 = "test_task_2"; |
| EXPECT_CALL(MockedCallBack, Call()).Times(0); |
| ASSERT_EQ(scheduler->PeriodicCall(MockedCallBack.AsStdFunction(), |
| absl::Milliseconds(100), task_name_1), |
| absl::OkStatus()); |
| ASSERT_EQ(scheduler->PeriodicCall(MockedCallBack.AsStdFunction(), |
| absl::Milliseconds(100), task_name_2), |
| absl::OkStatus()); |
| EXPECT_EQ(scheduler->CancelAll(), absl::OkStatus()); |
| EXPECT_EQ(scheduler->PeriodicCall([] { /*do nothing*/ }, |
| absl::Milliseconds(100), task_name_1), |
| absl::OkStatus()); |
| EXPECT_EQ(scheduler->PeriodicCall([] { /*do nothing*/ }, |
| absl::Milliseconds(100), task_name_2), |
| absl::OkStatus()); |
| test_io->run_for(std::chrono::milliseconds(1000)); |
| } |
| |
| TEST(Scheduler_fixtures, shutdown) { |
| testing::MockFunction<void()> MockedCallBack; |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| boost::asio::steady_timer testTimer(*test_io); |
| |
| constexpr absl::string_view task_name_1 = "test_task_1"; |
| constexpr absl::string_view task_name_2 = "test_task_2"; |
| EXPECT_CALL(MockedCallBack, Call()).Times(4); |
| ASSERT_EQ(scheduler->PeriodicCall(MockedCallBack.AsStdFunction(), |
| absl::Milliseconds(100), task_name_1), |
| absl::OkStatus()); |
| ASSERT_EQ(scheduler->PeriodicCall(MockedCallBack.AsStdFunction(), |
| absl::Milliseconds(100), task_name_2), |
| absl::OkStatus()); |
| |
| testTimer.expires_from_now(std::chrono::milliseconds(250)); |
| testTimer.async_wait([scheduler](const boost::system::error_code&) mutable { |
| EXPECT_EQ(scheduler->Shutdown(), absl::OkStatus()); |
| }); |
| // run the event loop until shutdown is called, and run() stops blocking |
| test_io->run(); |
| // continue running the event loop to ensure the timer is not called again |
| test_io->run_for(std::chrono::milliseconds(1000)); |
| } |
| |
| TEST(Scheduler_fixtures, SelfCancel) { |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| int count = 0; |
| EXPECT_EQ(scheduler->PeriodicCall( |
| [&count, &scheduler]() { |
| count++; |
| if (count == 5) { |
| EXPECT_EQ(scheduler->CancelCall("self_cancel_task"), |
| absl::OkStatus()); |
| } |
| }, |
| absl::Milliseconds(100), "self_cancel_task"), |
| absl::OkStatus()); |
| test_io->run_for(std::chrono::milliseconds(1000)); |
| EXPECT_EQ(count, 5); |
| } |
| |
| TEST(Scheduler_fixtures, RescheduleSelf) { |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| int count = 0; |
| // task A is allowed to reschedule itself |
| ASSERT_EQ(scheduler->DelayCall( |
| [&count, &scheduler]() { |
| count++; |
| ASSERT_EQ( |
| scheduler->DelayCall( |
| [&count, &scheduler]() { |
| count++; |
| ASSERT_EQ( |
| scheduler->DelayCall([&count]() { count++; }, |
| absl::Milliseconds(100), |
| "self_reschedule_task"), |
| absl::OkStatus()); |
| }, |
| absl::Milliseconds(100), "self_reschedule_task"), |
| absl::OkStatus()); |
| }, |
| absl::Milliseconds(100), "self_reschedule_task"), |
| absl::OkStatus()); |
| |
| test_io->run_for(std::chrono::milliseconds(500)); |
| EXPECT_EQ(count, 3); |
| } |
| |
| TEST(Scheduler_fixtures, RescheduleOther) { |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| int task_a_ct = 0; |
| ASSERT_EQ(scheduler->DelayCall( |
| [&task_a_ct]() { |
| task_a_ct++; |
| std::cerr << "task a ran"; |
| }, |
| absl::Milliseconds(200), "task_a"), |
| absl::OkStatus()); |
| |
| // task_a is already running, so it should fail from caller |
| ASSERT_EQ(scheduler->DelayCall([]() {}, absl::Milliseconds(50), "task_a"), |
| absl::AlreadyExistsError("Task already exists")); |
| test_io->run_for(std::chrono::milliseconds(500)); |
| EXPECT_EQ(task_a_ct, 1); |
| } |
| |
| TEST(Scheduler_fixtures, cancledTaskAreNotRun) { |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| testing::MockFunction<void()> MockedCallBack; |
| EXPECT_CALL(MockedCallBack, Call()).Times(testing::Exactly(0)); |
| ASSERT_EQ(scheduler->DelayCall(MockedCallBack.AsStdFunction(), |
| absl::Milliseconds(100), "task_a"), |
| absl::OkStatus()); |
| EXPECT_EQ(scheduler->CancelCall("task_a"), absl::OkStatus()); |
| test_io->run_for(std::chrono::milliseconds(200)); |
| } |
| |
| // The purpose of this test is to make sure that we are not incorrectly |
| // rounding down. If we were to request a delay of 10.5ms but it executes in |
| // 10ms, then any checks in callbacks that are time sensitive will not get |
| // triggered. Since we often calculate the delay as (time event should occur - |
| // now), we really do need to care about the precision. |
| TEST(Scheduler_fixtures, DelayCallPrecision) { |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| absl::Duration delay = absl::Milliseconds(10) + absl::Nanoseconds(500000); |
| bool ran = false; |
| absl::Time start = absl::Now(); |
| absl::Time end; |
| EXPECT_EQ(scheduler->DelayCall( |
| [&]() { |
| ran = true; |
| end = absl::Now(); |
| }, |
| delay, "delay_precision_test"), |
| absl::OkStatus()); |
| test_io->run_for( |
| absl::ToChronoMilliseconds(delay + absl::Milliseconds(20))); |
| EXPECT_TRUE(ran) << "callback did not run for delay " << delay; |
| EXPECT_GE(end - start, delay); |
| } |
| |
| // Note that `PeriodicCall` does not guarantee that callback (i) and callback |
| // (i+1) will be executed greater than or equal to the provided interval. |
| // Instead, it should schedule callback (j) greater than or equal to j |
| // intervals from the start time. |
| TEST(Scheduler_fixtures, PeriodicCallPrecision) { |
| auto test_io = std::make_shared<boost::asio::io_context>(); |
| std::shared_ptr<SchedulerInterface> scheduler = |
| std::make_shared<SchedulerBMC>(*test_io); |
| absl::Duration interval = absl::Milliseconds(10) + absl::Nanoseconds(500000); |
| std::vector<absl::Time> runtimes; |
| absl::Time start = absl::Now(); |
| int call_count = 0; |
| EXPECT_EQ(scheduler->PeriodicCall( |
| [&]() { |
| runtimes.push_back(absl::Now()); |
| call_count++; |
| }, |
| interval, "periodic_precision_test"), |
| absl::OkStatus()); |
| test_io->run_for(std::chrono::milliseconds(100)); |
| EXPECT_EQ(scheduler->CancelCall("periodic_precision_test"), absl::OkStatus()); |
| EXPECT_GE(call_count, 8); |
| // Note that the duration _between_ callbacks does not have to be greater |
| // than the wait interval! Instead, do the following check. |
| for (std::vector<absl::Time>::size_type i = 0; i < runtimes.size(); ++i) { |
| EXPECT_GE(runtimes[i] - start, (i + 1) * interval); |
| } |
| } |
| // TODO error conditions |
| // - write a test that cancels a non-existing task |
| // - write a test that cancels a task twice |
| // - write a test that schedules two test with the same name |
| // - both callback, and periodic |
| // - write a test that cancels all task, when there are not tasks (maybe) |
| |
| } // namespace |
| } // namespace safepower_agent |