316 lines
8.5 KiB
C++
316 lines
8.5 KiB
C++
// No copyright
|
|
#include "iface/common/task.hh"
|
|
#include "iface/common/task_test.hh"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
#include "iface/common/future.hh"
|
|
|
|
using namespace std::literals;
|
|
|
|
|
|
static_assert(nf7::TaskLike<nf7::Task<int32_t>>);
|
|
static_assert(nf7::TaskDriverLike<
|
|
nf7::test::SimpleTaskQueueDriverMock<nf7::Task<int32_t>>,
|
|
nf7::Task<int32_t>>);
|
|
|
|
TEST(Task, ExecAndThrow) {
|
|
const auto line = __LINE__ + 1;
|
|
nf7::Task<int32_t> task {[&](auto) { throw nf7::Exception {"hello"}; }};
|
|
|
|
try {
|
|
task(0);
|
|
EXPECT_FALSE("unreachable (exception expected)");
|
|
} catch (const nf7::Exception& e) {
|
|
EXPECT_EQ(e.location().line(), line);
|
|
EXPECT_EQ(e.location().file_name(), __FILE__);
|
|
}
|
|
}
|
|
|
|
TEST(TaskQueue, WrapLambdaWithArgs) {
|
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
|
auto wrapped = sut->Wrap([](const char*){});
|
|
|
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
|
|
|
wrapped("hello");
|
|
}
|
|
|
|
TEST(TaskQueue, WrapLambdaWithTaskParam) {
|
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
|
auto wrapped = sut->Wrap([](auto){});
|
|
|
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
|
|
|
wrapped();
|
|
}
|
|
|
|
TEST(TaskQueue, WrapLambdaWithArgsAndTaskParam) {
|
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
|
auto wrapped = sut->Wrap([](const char*, auto){});
|
|
|
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
|
|
|
wrapped("hello");
|
|
}
|
|
|
|
TEST(TaskQueue, WrapTask) {
|
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
|
auto wrapped = sut->Wrap(nf7::Task<int32_t> { [](auto){} });
|
|
|
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
|
|
|
wrapped();
|
|
}
|
|
|
|
TEST(TaskQueue, WrapTaskWithRef) {
|
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t&>>>();
|
|
|
|
auto wrapped = sut->Wrap(nf7::Task<int32_t&> { [](auto&){} });
|
|
|
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
|
|
|
wrapped();
|
|
}
|
|
|
|
TEST(TaskQueue, WrapInFutureThen) {
|
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
|
EXPECT_CALL(*sut, Push)
|
|
.WillOnce([](auto&& task) { task(int32_t {666}); });
|
|
|
|
nf7::Future<int32_t> fut {int32_t {777}};
|
|
|
|
auto called = uint32_t {0};
|
|
fut.Then(sut->Wrap([&](const auto& x) {
|
|
++called;
|
|
EXPECT_EQ(x, int32_t {777});
|
|
}));
|
|
|
|
EXPECT_EQ(called, uint32_t {1});
|
|
}
|
|
|
|
TEST(TaskQueue, WrapInFutureThenWithTaskParam) {
|
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
|
EXPECT_CALL(*sut, Push)
|
|
.WillOnce([](auto&& task) { task(int32_t {666}); });
|
|
|
|
nf7::Future<int32_t> fut {int32_t {777}};
|
|
|
|
auto called = uint32_t {0};
|
|
fut.Then(sut->Wrap([&](const auto& x, auto y) {
|
|
++called;
|
|
EXPECT_EQ(x, int32_t {777});
|
|
EXPECT_EQ(y, int32_t {666});
|
|
}));
|
|
|
|
EXPECT_EQ(called, uint32_t {1});
|
|
}
|
|
|
|
TEST(WrappedTaskQueue, Push) {
|
|
auto base = std::make_unique<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
|
EXPECT_CALL(*base, Push).Times(1);
|
|
|
|
class A : public nf7::TaskQueue<nf7::Task<int32_t>> {
|
|
public:
|
|
A() = default;
|
|
};
|
|
nf7::WrappedTaskQueue<A> sut {std::move(base)};
|
|
static_assert(std::is_base_of_v<A, decltype(sut)>,
|
|
"WrappedTaskQueue doesn't based on base type");
|
|
|
|
sut.Push(nf7::Task<int32_t> {[](auto){}});
|
|
|
|
// ensure all templates legal
|
|
(std::void_t<decltype(sut.Wrap([](auto){}))>) 0;
|
|
(std::void_t<decltype(sut.Exec([](auto){}))>) 0;
|
|
}
|
|
|
|
TEST(SimpleTaskQueue, PushAndDrive) {
|
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
|
nf7::test::SimpleTaskQueueDriverMock<nf7::Task<int32_t&>> driver;
|
|
|
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
|
|
|
auto interrupt = false;
|
|
ON_CALL(driver, nextIdleInterruption)
|
|
.WillByDefault([&]() { return interrupt; });
|
|
|
|
auto ctx = int32_t {0};
|
|
|
|
::testing::Sequence s;
|
|
EXPECT_CALL(driver, BeginBusy)
|
|
.Times(1)
|
|
.InSequence(s);
|
|
EXPECT_CALL(driver, Drive)
|
|
.Times(1)
|
|
.InSequence(s)
|
|
.WillOnce([&](auto&& task) { task(ctx); });
|
|
EXPECT_CALL(driver, EndBusy)
|
|
.Times(1)
|
|
.InSequence(s)
|
|
.WillOnce([&]() { interrupt = true; });
|
|
|
|
auto called = uint32_t {0};
|
|
sut.Exec([&](auto&){ ++called; });
|
|
sut.Drive(driver);
|
|
|
|
EXPECT_EQ(called, 1);
|
|
}
|
|
|
|
TEST(SimpleTaskQueue, PushWithDelayAndDrive) {
|
|
constexpr auto dur = 100ms;
|
|
|
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
|
nf7::test::SimpleTaskQueueDriverMock<nf7::Task<int32_t&>> driver;
|
|
|
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
|
|
|
auto tick = 0ms;
|
|
auto ctx = int32_t {0};
|
|
auto cycle = uint32_t {0};
|
|
auto interrupt = false;
|
|
|
|
ON_CALL(driver, BeginBusy)
|
|
.WillByDefault([&]() {
|
|
if (++cycle == 2) {
|
|
tick += dur;
|
|
}
|
|
});
|
|
ON_CALL(driver, Drive)
|
|
.WillByDefault([&](auto&& task) { task(ctx); });
|
|
ON_CALL(driver, tick)
|
|
.WillByDefault([&]() { return nf7::Task<int32_t&>::Time {tick}; });
|
|
ON_CALL(driver, nextIdleInterruption)
|
|
.WillByDefault([&]() { return interrupt; });
|
|
|
|
const auto expect_at = std::chrono::system_clock::now() + dur;
|
|
decltype(std::chrono::system_clock::now()) actual_at;
|
|
|
|
sut.Push(nf7::Task<int32_t&> { nf7::Task<int32_t>::Time {dur}, [&](auto&){
|
|
actual_at = std::chrono::system_clock::now();
|
|
interrupt = true;
|
|
}});
|
|
|
|
sut.Drive(driver);
|
|
|
|
EXPECT_GE(actual_at, expect_at);
|
|
}
|
|
|
|
TEST(SimpleTaskQueue, PushWithDelayAndDriveOrderly) {
|
|
using Task = nf7::Task<int32_t&>;
|
|
|
|
nf7::test::SimpleTaskQueueMock<Task> sut;
|
|
nf7::test::SimpleTaskQueueDriverMock<Task> driver;
|
|
|
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
|
|
|
auto tick = 0s;
|
|
auto interrupt = false;
|
|
auto ctx = int32_t {0};
|
|
|
|
ON_CALL(driver, Drive)
|
|
.WillByDefault([&](auto&& task) { task(ctx); });
|
|
ON_CALL(driver, EndBusy)
|
|
.WillByDefault([&]() { interrupt = true; });
|
|
ON_CALL(driver, tick)
|
|
.WillByDefault([&]() { return Task::Time {tick}; });
|
|
ON_CALL(driver, nextIdleInterruption)
|
|
.WillByDefault([&]() { return interrupt; });
|
|
|
|
auto called_after = uint32_t {0};
|
|
auto called_immediately = uint32_t {0};
|
|
sut.Push(Task {Task::Time {1s}, [&](auto&){ ++called_after; }});
|
|
sut.Push(Task {Task::Time {0s}, [&](auto&){ ++called_immediately; }});
|
|
|
|
interrupt = false;
|
|
sut.Drive(driver);
|
|
|
|
EXPECT_EQ(called_after, 0);
|
|
EXPECT_EQ(called_immediately, 1);
|
|
|
|
interrupt = false;
|
|
++tick;
|
|
sut.Drive(driver);
|
|
|
|
EXPECT_EQ(called_after, 1);
|
|
EXPECT_EQ(called_immediately, 1);
|
|
}
|
|
|
|
TEST(SimpleTaskQueue, ChaoticPushAndDrive) {
|
|
constexpr auto kThreads = uint32_t {32};
|
|
constexpr auto kPushPerThread = uint32_t {100};
|
|
|
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
|
|
|
// use NiceMock to suppress annoying warnings that slowed unittests
|
|
::testing::NiceMock<
|
|
nf7::test::SimpleTaskQueueDriverMock<nf7::Task<int32_t&>>> driver;
|
|
|
|
auto ctx = int32_t {0};
|
|
auto values = std::vector<uint32_t> (kThreads);
|
|
auto threads = std::vector<std::thread> (kThreads);
|
|
auto exitedThreads = std::atomic<uint32_t> {0};
|
|
|
|
ON_CALL(driver, Drive)
|
|
.WillByDefault([&](auto&& task) { task(ctx); });
|
|
ON_CALL(driver, nextIdleInterruption)
|
|
.WillByDefault([&]() { return exitedThreads >= kThreads; });
|
|
|
|
for (uint32_t i = 0; i < kThreads; ++i) {
|
|
threads[i] = std::thread {[&, i](){
|
|
for (uint32_t j = 0; j < kPushPerThread; ++j) {
|
|
sut.Exec([&, i](auto&){ ++values[i]; });
|
|
}
|
|
sut.Exec([&](auto&){ ++exitedThreads; });
|
|
}};
|
|
}
|
|
for (auto& th : threads) {
|
|
th.join();
|
|
}
|
|
sut.Drive(driver);
|
|
|
|
for (const auto execCount : values) {
|
|
EXPECT_EQ(execCount, kPushPerThread);
|
|
}
|
|
}
|
|
|
|
TEST(SimpleTaskQueue, WaitForEmpty) {
|
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
|
|
|
// use NiceMock to suppress annoying warnings that slowed unittests
|
|
::testing::NiceMock<
|
|
nf7::test::SimpleTaskQueueDriverMock<nf7::Task<int32_t&>>> driver;
|
|
|
|
for (uint32_t i = 0; i < 1000; ++i) {
|
|
sut.Exec([](auto&){});
|
|
}
|
|
|
|
auto ctx = int32_t {0};
|
|
ON_CALL(driver, Drive)
|
|
.WillByDefault([&](auto&& task) { task(ctx); });
|
|
|
|
std::atomic<bool> exit = false;
|
|
ON_CALL(driver, nextIdleInterruption)
|
|
.WillByDefault([&]() -> bool { return exit; });
|
|
|
|
std::thread th {[&]() { sut.Drive(driver); }};
|
|
EXPECT_TRUE(sut.WaitForEmpty(1s));
|
|
|
|
exit = true;
|
|
sut.Wake();
|
|
th.join();
|
|
}
|
|
|
|
TEST(SimpleTaskQueue, WaitForEmptyWhenEmpty) {
|
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
|
EXPECT_TRUE(sut.WaitForEmpty(1s));
|
|
}
|