blob: c654bd7792b25bc1ad2aab8b99c223f2c0f9fe7d [file] [log] [blame]
#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