Compare commits
2 Commits
74a52f0781
...
5c6d72c649
Author | SHA1 | Date | |
---|---|---|---|
5c6d72c649 | |||
c58d2bb1e5 |
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <concepts>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -22,15 +23,43 @@
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename T>
|
||||||
|
concept TaskLike = requires (T& x) {
|
||||||
|
typename T::Param;
|
||||||
|
typename T::Time;
|
||||||
|
|
||||||
|
T([](typename T::Param){}, std::source_location {});
|
||||||
|
requires std::copy_constructible<T>;
|
||||||
|
requires std::move_constructible<T>;
|
||||||
|
requires std::invocable<T, typename T::Param>;
|
||||||
|
|
||||||
|
{x.operator<=>(x)} noexcept;
|
||||||
|
{x.after()} noexcept -> std::convertible_to<typename T::Time>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename Item>
|
||||||
|
concept TaskDriverLike = requires (T& x, Item&& y) {
|
||||||
|
requires TaskLike<Item>;
|
||||||
|
|
||||||
|
{x.BeginBusy()} noexcept;
|
||||||
|
{x.Drive(std::move(y))} noexcept;
|
||||||
|
{x.EndBusy()} noexcept;
|
||||||
|
{x.tick()} noexcept -> std::convertible_to<typename Item::Time>;
|
||||||
|
{x.nextIdleInterruption()} noexcept -> std::convertible_to<bool>;
|
||||||
|
{x.nextTaskInterruption()} noexcept -> std::convertible_to<bool>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename P>
|
||||||
class Task final {
|
class Task final {
|
||||||
public:
|
public:
|
||||||
using Time = std::chrono::sys_time<std::chrono::milliseconds>;
|
using Param = P;
|
||||||
using Function = std::function<void(Args&&...)>;
|
using Time = std::chrono::sys_time<std::chrono::milliseconds>;
|
||||||
|
using Func = std::function<void(Param)>;
|
||||||
|
|
||||||
Task() = delete;
|
Task() = delete;
|
||||||
explicit Task(
|
explicit Task(
|
||||||
Function&& func,
|
Func&& func,
|
||||||
std::source_location location = std::source_location::current()) noexcept
|
std::source_location location = std::source_location::current()) noexcept
|
||||||
: func_(std::move(func)),
|
: func_(std::move(func)),
|
||||||
location_(location) {
|
location_(location) {
|
||||||
@ -38,7 +67,7 @@ class Task final {
|
|||||||
}
|
}
|
||||||
Task(
|
Task(
|
||||||
Time after,
|
Time after,
|
||||||
Function&& func,
|
Func&& func,
|
||||||
std::source_location location = std::source_location::current()) noexcept
|
std::source_location location = std::source_location::current()) noexcept
|
||||||
: after_(after),
|
: after_(after),
|
||||||
func_(std::move(func)),
|
func_(std::move(func)),
|
||||||
@ -55,9 +84,9 @@ class Task final {
|
|||||||
return after_ <=> other.after_;
|
return after_ <=> other.after_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(Args&&... args) {
|
void operator()(Param param) {
|
||||||
try {
|
try {
|
||||||
func_(std::forward<Args>(args)...);
|
func_(std::forward<Param>(param));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
throw Exception {"task throws an exception", location_};
|
throw Exception {"task throws an exception", location_};
|
||||||
}
|
}
|
||||||
@ -68,16 +97,15 @@ class Task final {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Time after_;
|
Time after_;
|
||||||
|
Func func_;
|
||||||
Function func_;
|
|
||||||
|
|
||||||
std::source_location location_;
|
std::source_location location_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Args>
|
template <TaskLike T>
|
||||||
class TaskQueue : public std::enable_shared_from_this<TaskQueue<Args...>> {
|
class TaskQueue : public std::enable_shared_from_this<TaskQueue<T>> {
|
||||||
public:
|
public:
|
||||||
using Item = Task<Args...>;
|
using Item = T;
|
||||||
|
using Param = typename Item::Param;
|
||||||
|
|
||||||
TaskQueue() = default;
|
TaskQueue() = default;
|
||||||
virtual ~TaskQueue() = default;
|
virtual ~TaskQueue() = default;
|
||||||
@ -103,19 +131,16 @@ class TaskQueue : public std::enable_shared_from_this<TaskQueue<Args...>> {
|
|||||||
auto Wrap(
|
auto Wrap(
|
||||||
auto&& f,
|
auto&& f,
|
||||||
std::source_location loc = std::source_location::current()) noexcept {
|
std::source_location loc = std::source_location::current()) noexcept {
|
||||||
using F = decltype(f);
|
return [self = shared_from_this(), f = std::move(f), loc](auto&&... args)
|
||||||
|
|
||||||
return [self = shared_from_this(), f = std::move(f), loc](auto&&... args1)
|
|
||||||
mutable {
|
mutable {
|
||||||
|
using F = decltype(f);
|
||||||
self->Push(Item {
|
self->Push(Item {
|
||||||
[f = std::move(f), ...args1 = std::forward<decltype(args1)>(args1)]
|
[f = std::move(f), ...args = std::forward<decltype(args)>(args)]
|
||||||
(auto&&... args2) mutable {
|
(Param p) mutable {
|
||||||
if constexpr (
|
if constexpr (std::is_invocable_v<F, decltype(args)..., Param>) {
|
||||||
std::is_invocable_v<F, decltype(args1)..., decltype(args2)...>) {
|
f(std::forward<decltype(args)>(args)..., std::forward<Param>(p));
|
||||||
f(std::forward<decltype(args1)>(args1)...,
|
} else if constexpr (std::is_invocable_v<F, decltype(args)...>) {
|
||||||
std::forward<decltype(args2)>(args2)...);
|
f(std::forward<decltype(args)>(args)...);
|
||||||
} else if constexpr (std::is_invocable_v<F, decltype(args1)...>) {
|
|
||||||
f(std::forward<decltype(args1)>(args1)...);
|
|
||||||
} else {
|
} else {
|
||||||
static_assert(false, "the wrapped function is invalid");
|
static_assert(false, "the wrapped function is invalid");
|
||||||
}
|
}
|
||||||
@ -128,7 +153,7 @@ class TaskQueue : public std::enable_shared_from_this<TaskQueue<Args...>> {
|
|||||||
// THREAD SAFE
|
// THREAD SAFE
|
||||||
template <typename R>
|
template <typename R>
|
||||||
Future<R> ExecAnd(
|
Future<R> ExecAnd(
|
||||||
std::function<R(Args&&...)>&& f,
|
std::function<R(Param)>&& f,
|
||||||
std::source_location loc = std::source_location::current()) noexcept {
|
std::source_location loc = std::source_location::current()) noexcept {
|
||||||
return ExecAnd({}, std::move(f));
|
return ExecAnd({}, std::move(f));
|
||||||
}
|
}
|
||||||
@ -136,35 +161,35 @@ class TaskQueue : public std::enable_shared_from_this<TaskQueue<Args...>> {
|
|||||||
// THREAD SAFE
|
// THREAD SAFE
|
||||||
template <typename R>
|
template <typename R>
|
||||||
Future<R> ExecAnd(
|
Future<R> ExecAnd(
|
||||||
Future<R>::Completer&& comp,
|
Future<R>::Completer&& cmp,
|
||||||
std::function<R(Args&&...)>&& f,
|
std::function<R(Param)>&& f,
|
||||||
std::source_location loc = std::source_location::current()) noexcept {
|
std::source_location loc = std::source_location::current()) noexcept {
|
||||||
Future<R> future {comp};
|
Future<R> future {cmp};
|
||||||
Push(Item { [f = std::move(f), comp = std::move(comp)](Args&&...) mutable {
|
Push(Item { [f = std::move(f), cmp = std::move(cmp)](Param) mutable {
|
||||||
comp.Exec(f);
|
cmp.Exec(f);
|
||||||
}, loc});
|
}, loc});
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
// THREAD SAFE
|
// THREAD SAFE
|
||||||
void Exec(
|
void Exec(
|
||||||
std::function<void()>&& f,
|
std::function<void(Param)>&& f,
|
||||||
std::source_location loc = std::source_location::current()) noexcept {
|
std::source_location loc = std::source_location::current()) noexcept {
|
||||||
Push(Item {std::move(f), loc});
|
Push(Item {std::move(f), loc});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using std::enable_shared_from_this<TaskQueue<Args...>>::shared_from_this;
|
using std::enable_shared_from_this<TaskQueue<Item>>::shared_from_this;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename I>
|
||||||
class WrappedTaskQueue : public T {
|
class WrappedTaskQueue : public I {
|
||||||
public:
|
public:
|
||||||
static_assert(std::is_base_of_v<TaskQueue<Args...>, T>,
|
static_assert(std::is_base_of_v<TaskQueue<typename I::Item>, I>,
|
||||||
"base type should be based on TaskQueue");
|
"base type should be based on TaskQueue");
|
||||||
|
|
||||||
using Inside = TaskQueue<Args...>;
|
using Item = typename I::Item;
|
||||||
using Item = Task<Args...>;
|
using Inside = TaskQueue<Item>;
|
||||||
|
|
||||||
WrappedTaskQueue() = delete;
|
WrappedTaskQueue() = delete;
|
||||||
explicit WrappedTaskQueue(std::unique_ptr<Inside>&& q) noexcept
|
explicit WrappedTaskQueue(std::unique_ptr<Inside>&& q) noexcept
|
||||||
@ -184,31 +209,11 @@ class WrappedTaskQueue : public T {
|
|||||||
std::unique_ptr<Inside> q_;
|
std::unique_ptr<Inside> q_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Args>
|
template <TaskLike T>
|
||||||
class SimpleTaskQueue : public TaskQueue<Args...> {
|
class SimpleTaskQueue : public TaskQueue<T> {
|
||||||
public:
|
public:
|
||||||
using Item = Task<Args...>;
|
using Item = T;
|
||||||
using Time = Item::Time;
|
using Time = typename Item::Time;
|
||||||
|
|
||||||
class Driver {
|
|
||||||
public:
|
|
||||||
Driver() = default;
|
|
||||||
virtual ~Driver() = default;
|
|
||||||
|
|
||||||
Driver(const Driver&) = delete;
|
|
||||||
Driver(Driver&&) = delete;
|
|
||||||
Driver& operator=(const Driver&) = delete;
|
|
||||||
Driver& operator=(Driver&&) = delete;
|
|
||||||
|
|
||||||
virtual void BeginBusy() noexcept { }
|
|
||||||
virtual void EndBusy() noexcept { }
|
|
||||||
|
|
||||||
virtual std::tuple<Args...> params() const noexcept = 0;
|
|
||||||
virtual Time tick() const noexcept { return {}; }
|
|
||||||
|
|
||||||
virtual bool nextIdleInterruption() const noexcept { return false; }
|
|
||||||
virtual bool nextTaskInterruption() const noexcept { return false; }
|
|
||||||
};
|
|
||||||
|
|
||||||
SimpleTaskQueue() = default;
|
SimpleTaskQueue() = default;
|
||||||
|
|
||||||
@ -229,10 +234,8 @@ class SimpleTaskQueue : public TaskQueue<Args...> {
|
|||||||
cv_.notify_all();
|
cv_.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <TaskDriverLike<Item> Driver>
|
||||||
typename T,
|
void Drive(Driver& driver) {
|
||||||
typename = std::enable_if<std::is_base_of_v<Driver, T>, void>>
|
|
||||||
void Drive(T& driver) {
|
|
||||||
while (!driver.nextIdleInterruption()) {
|
while (!driver.nextIdleInterruption()) {
|
||||||
driver.BeginBusy();
|
driver.BeginBusy();
|
||||||
try {
|
try {
|
||||||
@ -245,11 +248,7 @@ class SimpleTaskQueue : public TaskQueue<Args...> {
|
|||||||
tasks_.pop();
|
tasks_.pop();
|
||||||
k.unlock();
|
k.unlock();
|
||||||
|
|
||||||
try {
|
driver.Drive(std::move(task));
|
||||||
std::apply(task, driver.params());
|
|
||||||
} catch (...) {
|
|
||||||
onErrorWhileExec(task.location());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (const std::system_error&) {
|
} catch (const std::system_error&) {
|
||||||
driver.EndBusy();
|
driver.EndBusy();
|
||||||
@ -260,11 +259,11 @@ class SimpleTaskQueue : public TaskQueue<Args...> {
|
|||||||
try {
|
try {
|
||||||
std::unique_lock<std::mutex> k{mtx_};
|
std::unique_lock<std::mutex> k{mtx_};
|
||||||
|
|
||||||
const auto until = nextAwakeTime();
|
const auto until = nextAwake();
|
||||||
const auto pred = [&]() {
|
const auto pred = [&]() {
|
||||||
return
|
return
|
||||||
!CheckIfSleeping(driver.tick()) ||
|
!CheckIfSleeping(driver.tick()) ||
|
||||||
(until && *until > nextAwakeTime().value_or(Time::max())) ||
|
until.value_or(Time::max()) > nextAwake().value_or(Time::max()) ||
|
||||||
driver.nextIdleInterruption();
|
driver.nextIdleInterruption();
|
||||||
};
|
};
|
||||||
if (std::nullopt != until) {
|
if (std::nullopt != until) {
|
||||||
@ -289,7 +288,7 @@ class SimpleTaskQueue : public TaskQueue<Args...> {
|
|||||||
bool CheckIfSleeping(Time now) const noexcept {
|
bool CheckIfSleeping(Time now) const noexcept {
|
||||||
return tasks_.empty() || tasks_.top().after() > now;
|
return tasks_.empty() || tasks_.top().after() > now;
|
||||||
}
|
}
|
||||||
std::optional<Time> nextAwakeTime() const noexcept {
|
std::optional<Time> nextAwake() const noexcept {
|
||||||
if (tasks_.empty()) {
|
if (tasks_.empty()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,17 @@
|
|||||||
using namespace std::literals;
|
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) {
|
TEST(Task, ExecAndThrow) {
|
||||||
const auto line = __LINE__ + 1;
|
const auto line = __LINE__ + 1;
|
||||||
nf7::Task<> task {[&]() { throw nf7::Exception {"hello"}; }};
|
nf7::Task<int32_t> task {[&](auto) { throw nf7::Exception {"hello"}; }};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
task();
|
task(0);
|
||||||
EXPECT_FALSE("unreachable (exception expected)");
|
EXPECT_FALSE("unreachable (exception expected)");
|
||||||
} catch (const nf7::Exception& e) {
|
} catch (const nf7::Exception& e) {
|
||||||
EXPECT_EQ(e.location().line(), line);
|
EXPECT_EQ(e.location().line(), line);
|
||||||
@ -28,39 +33,46 @@ TEST(Task, ExecAndThrow) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Task, ExecWithParam) {
|
TEST(TaskQueue, WrapLambdaWithArgs) {
|
||||||
auto called = uint32_t {0};
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
||||||
nf7::Task<uint32_t, uint32_t> task {[&](auto x, auto y) {
|
auto wrapped = sut->Wrap([](const char*){});
|
||||||
++called;
|
|
||||||
EXPECT_EQ(x, 0);
|
|
||||||
EXPECT_EQ(y, 1);
|
|
||||||
}};
|
|
||||||
task(0, 1);
|
|
||||||
|
|
||||||
EXPECT_EQ(called, 1);
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
||||||
|
|
||||||
|
wrapped("hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TaskQueue, WrapLambda) {
|
TEST(TaskQueue, WrapLambdaWithTaskParam) {
|
||||||
auto sut = std::make_shared<nf7::test::TaskQueueMock<>>();
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
||||||
auto wrapped = sut->Wrap([](){});
|
auto wrapped = sut->Wrap([](auto){});
|
||||||
|
|
||||||
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
||||||
|
|
||||||
wrapped();
|
wrapped();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TaskQueue, WrapLambdaWithParam) {
|
TEST(TaskQueue, WrapLambdaWithArgsAndTaskParam) {
|
||||||
auto sut = std::make_shared<nf7::test::TaskQueueMock<uint32_t>>();
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
||||||
auto wrapped = sut->Wrap([](uint32_t){});
|
auto wrapped = sut->Wrap([](const char*, auto){});
|
||||||
|
|
||||||
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
||||||
|
|
||||||
wrapped();
|
wrapped("hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TaskQueue, WrapTask) {
|
TEST(TaskQueue, WrapTask) {
|
||||||
auto sut = std::make_shared<nf7::test::TaskQueueMock<>>();
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
||||||
auto wrapped = sut->Wrap(nf7::Task<> { nf7::Task<>::Time {0ms}, [](){} });
|
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);
|
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
||||||
|
|
||||||
@ -68,9 +80,9 @@ TEST(TaskQueue, WrapTask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(TaskQueue, WrapInFutureThen) {
|
TEST(TaskQueue, WrapInFutureThen) {
|
||||||
auto sut = std::make_shared<nf7::test::TaskQueueMock<>>();
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
||||||
EXPECT_CALL(*sut, Push)
|
EXPECT_CALL(*sut, Push)
|
||||||
.WillOnce([](auto&& task) { task(); });
|
.WillOnce([](auto&& task) { task(int32_t {666}); });
|
||||||
|
|
||||||
nf7::Future<int32_t> fut {int32_t {777}};
|
nf7::Future<int32_t> fut {int32_t {777}};
|
||||||
|
|
||||||
@ -80,13 +92,13 @@ TEST(TaskQueue, WrapInFutureThen) {
|
|||||||
EXPECT_EQ(x, int32_t {777});
|
EXPECT_EQ(x, int32_t {777});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
EXPECT_EQ(called, 1);
|
EXPECT_EQ(called, uint32_t {1});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TaskQueue, WrapInFutureThenWithParam) {
|
TEST(TaskQueue, WrapInFutureThenWithTaskParam) {
|
||||||
auto sut = std::make_shared<nf7::test::TaskQueueMock<uint32_t>>();
|
auto sut = std::make_shared<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
||||||
EXPECT_CALL(*sut, Push)
|
EXPECT_CALL(*sut, Push)
|
||||||
.WillOnce([](auto&& task) { task(666); });
|
.WillOnce([](auto&& task) { task(int32_t {666}); });
|
||||||
|
|
||||||
nf7::Future<int32_t> fut {int32_t {777}};
|
nf7::Future<int32_t> fut {int32_t {777}};
|
||||||
|
|
||||||
@ -97,14 +109,14 @@ TEST(TaskQueue, WrapInFutureThenWithParam) {
|
|||||||
EXPECT_EQ(y, int32_t {666});
|
EXPECT_EQ(y, int32_t {666});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
EXPECT_EQ(called, 1);
|
EXPECT_EQ(called, uint32_t {1});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WrappedTaskQueue, Push) {
|
TEST(WrappedTaskQueue, Push) {
|
||||||
auto base = std::make_unique<nf7::test::TaskQueueMock<>>();
|
auto base = std::make_unique<nf7::test::TaskQueueMock<nf7::Task<int32_t>>>();
|
||||||
EXPECT_CALL(*base, Push).Times(1);
|
EXPECT_CALL(*base, Push).Times(1);
|
||||||
|
|
||||||
class A : public nf7::TaskQueue<> {
|
class A : public nf7::TaskQueue<nf7::Task<int32_t>> {
|
||||||
public:
|
public:
|
||||||
A() = default;
|
A() = default;
|
||||||
};
|
};
|
||||||
@ -112,52 +124,41 @@ TEST(WrappedTaskQueue, Push) {
|
|||||||
static_assert(std::is_base_of_v<A, decltype(sut)>,
|
static_assert(std::is_base_of_v<A, decltype(sut)>,
|
||||||
"WrappedTaskQueue doesn't based on base type");
|
"WrappedTaskQueue doesn't based on base type");
|
||||||
|
|
||||||
sut.Push(nf7::Task<> {[](){}});
|
sut.Push(nf7::Task<int32_t> {[](auto){}});
|
||||||
|
|
||||||
// ensure all templates available
|
// ensure all templates legal
|
||||||
(std::void_t<decltype(sut.Wrap([](){}))>) 0;
|
(std::void_t<decltype(sut.Wrap([](auto){}))>) 0;
|
||||||
(std::void_t<decltype(sut.ExecAnd<uint32_t>([](){ return 0; }))>) 0;
|
(std::void_t<decltype(sut.ExecAnd<uint32_t>([](auto){ return 0; }))>) 0;
|
||||||
(std::void_t<decltype(sut.Exec([](){}))>) 0;
|
(std::void_t<decltype(sut.Exec([](auto){}))>) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimpleTaskQueue, PushAndDrive) {
|
TEST(SimpleTaskQueue, PushAndDrive) {
|
||||||
nf7::test::SimpleTaskQueueMock<> sut;
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
||||||
|
nf7::test::SimpleTaskQueueDriverMock<nf7::Task<int32_t&>> driver;
|
||||||
|
|
||||||
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
||||||
EXPECT_CALL(sut, onErrorWhileExec).Times(0);
|
|
||||||
|
|
||||||
auto interrupt = false;
|
auto interrupt = false;
|
||||||
::testing::NiceMock<nf7::test::SimpleTaskQueueDriverMock<>> driver;
|
|
||||||
ON_CALL(driver, EndBusy)
|
|
||||||
.WillByDefault([&]() { interrupt = true; });
|
|
||||||
ON_CALL(driver, nextIdleInterruption)
|
ON_CALL(driver, nextIdleInterruption)
|
||||||
.WillByDefault([&]() { return interrupt; });
|
.WillByDefault([&]() { return interrupt; });
|
||||||
|
|
||||||
auto called = uint32_t {0};
|
auto ctx = int32_t {0};
|
||||||
sut.Push(nf7::Task<> {[&](){ ++called; }});
|
|
||||||
sut.Drive(driver);
|
|
||||||
|
|
||||||
EXPECT_EQ(called, 1);
|
::testing::Sequence s;
|
||||||
}
|
EXPECT_CALL(driver, BeginBusy)
|
||||||
|
.Times(1)
|
||||||
TEST(SimpleTaskQueue, PushAndDriveWithParam) {
|
.InSequence(s);
|
||||||
nf7::test::SimpleTaskQueueMock<uint32_t> sut;
|
EXPECT_CALL(driver, Drive)
|
||||||
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
.Times(1)
|
||||||
EXPECT_CALL(sut, onErrorWhileExec).Times(0);
|
.InSequence(s)
|
||||||
|
.WillOnce([&](auto&& task) { task(ctx); });
|
||||||
auto interrupt = false;
|
EXPECT_CALL(driver, EndBusy)
|
||||||
::testing::NiceMock<nf7::test::SimpleTaskQueueDriverMock<uint32_t>> driver;
|
.Times(1)
|
||||||
ON_CALL(driver, EndBusy)
|
.InSequence(s)
|
||||||
.WillByDefault([&]() { interrupt = true; });
|
.WillOnce([&]() { interrupt = true; });
|
||||||
ON_CALL(driver, nextIdleInterruption)
|
|
||||||
.WillByDefault([&]() { return interrupt; });
|
|
||||||
ON_CALL(driver, params)
|
|
||||||
.WillByDefault([&]() { return std::tuple<uint32_t> {666}; });
|
|
||||||
|
|
||||||
auto called = uint32_t {0};
|
auto called = uint32_t {0};
|
||||||
sut.Push(nf7::Task<uint32_t> {[&](auto x){
|
sut.Exec([&](auto&){ ++called; });
|
||||||
EXPECT_EQ(x, 666);
|
|
||||||
++called;
|
|
||||||
}});
|
|
||||||
sut.Drive(driver);
|
sut.Drive(driver);
|
||||||
|
|
||||||
EXPECT_EQ(called, 1);
|
EXPECT_EQ(called, 1);
|
||||||
@ -166,30 +167,33 @@ TEST(SimpleTaskQueue, PushAndDriveWithParam) {
|
|||||||
TEST(SimpleTaskQueue, PushWithDelayAndDrive) {
|
TEST(SimpleTaskQueue, PushWithDelayAndDrive) {
|
||||||
constexpr auto dur = 100ms;
|
constexpr auto dur = 100ms;
|
||||||
|
|
||||||
auto tick = 0ms;
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
||||||
|
nf7::test::SimpleTaskQueueDriverMock<nf7::Task<int32_t&>> driver;
|
||||||
|
|
||||||
nf7::test::SimpleTaskQueueMock<> sut;
|
|
||||||
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
||||||
EXPECT_CALL(sut, onErrorWhileExec).Times(0);
|
|
||||||
|
|
||||||
auto cycle = uint32_t {0};
|
auto tick = 0ms;
|
||||||
|
auto ctx = int32_t {0};
|
||||||
|
auto cycle = uint32_t {0};
|
||||||
auto interrupt = false;
|
auto interrupt = false;
|
||||||
::testing::NiceMock<nf7::test::SimpleTaskQueueDriverMock<>> driver;
|
|
||||||
ON_CALL(driver, BeginBusy)
|
ON_CALL(driver, BeginBusy)
|
||||||
.WillByDefault([&]() {
|
.WillByDefault([&]() {
|
||||||
if (++cycle == 2) {
|
if (++cycle == 2) {
|
||||||
tick += dur;
|
tick += dur;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ON_CALL(driver, Drive)
|
||||||
|
.WillByDefault([&](auto&& task) { task(ctx); });
|
||||||
ON_CALL(driver, tick)
|
ON_CALL(driver, tick)
|
||||||
.WillByDefault([&]() { return nf7::Task<>::Time {tick}; });
|
.WillByDefault([&]() { return nf7::Task<int32_t&>::Time {tick}; });
|
||||||
ON_CALL(driver, nextIdleInterruption)
|
ON_CALL(driver, nextIdleInterruption)
|
||||||
.WillByDefault([&]() { return interrupt; });
|
.WillByDefault([&]() { return interrupt; });
|
||||||
|
|
||||||
const auto expect_at = std::chrono::system_clock::now() + dur;
|
const auto expect_at = std::chrono::system_clock::now() + dur;
|
||||||
decltype(std::chrono::system_clock::now()) actual_at;
|
decltype(std::chrono::system_clock::now()) actual_at;
|
||||||
|
|
||||||
sut.Push(nf7::Task<> { nf7::Task<>::Time {dur}, [&](){
|
sut.Push(nf7::Task<int32_t&> { nf7::Task<int32_t>::Time {dur}, [&](auto&){
|
||||||
actual_at = std::chrono::system_clock::now();
|
actual_at = std::chrono::system_clock::now();
|
||||||
interrupt = true;
|
interrupt = true;
|
||||||
}});
|
}});
|
||||||
@ -200,25 +204,30 @@ TEST(SimpleTaskQueue, PushWithDelayAndDrive) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimpleTaskQueue, PushWithDelayAndDriveOrderly) {
|
TEST(SimpleTaskQueue, PushWithDelayAndDriveOrderly) {
|
||||||
auto tick = 0s;
|
using Task = nf7::Task<int32_t&>;
|
||||||
|
|
||||||
|
nf7::test::SimpleTaskQueueMock<Task> sut;
|
||||||
|
nf7::test::SimpleTaskQueueDriverMock<Task> driver;
|
||||||
|
|
||||||
nf7::test::SimpleTaskQueueMock<> sut;
|
|
||||||
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
||||||
EXPECT_CALL(sut, onErrorWhileExec).Times(0);
|
|
||||||
|
|
||||||
|
auto tick = 0s;
|
||||||
auto interrupt = false;
|
auto interrupt = false;
|
||||||
::testing::NiceMock<nf7::test::SimpleTaskQueueDriverMock<>> driver;
|
auto ctx = int32_t {0};
|
||||||
|
|
||||||
|
ON_CALL(driver, Drive)
|
||||||
|
.WillByDefault([&](auto&& task) { task(ctx); });
|
||||||
ON_CALL(driver, EndBusy)
|
ON_CALL(driver, EndBusy)
|
||||||
.WillByDefault([&]() { interrupt = true; });
|
.WillByDefault([&]() { interrupt = true; });
|
||||||
ON_CALL(driver, tick)
|
ON_CALL(driver, tick)
|
||||||
.WillByDefault([&]() { return nf7::Task<>::Time {tick}; });
|
.WillByDefault([&]() { return Task::Time {tick}; });
|
||||||
ON_CALL(driver, nextIdleInterruption)
|
ON_CALL(driver, nextIdleInterruption)
|
||||||
.WillByDefault([&]() { return interrupt; });
|
.WillByDefault([&]() { return interrupt; });
|
||||||
|
|
||||||
auto called_after = uint32_t {0};
|
auto called_after = uint32_t {0};
|
||||||
auto called_immediately = uint32_t {0};
|
auto called_immediately = uint32_t {0};
|
||||||
sut.Push(nf7::Task<> {nf7::Task<>::Time {1s}, [&](){ ++called_after; }});
|
sut.Push(Task {Task::Time {1s}, [&](auto&){ ++called_after; }});
|
||||||
sut.Push(nf7::Task<> {nf7::Task<>::Time {0s}, [&](){ ++called_immediately; }});
|
sut.Push(Task {Task::Time {0s}, [&](auto&){ ++called_immediately; }});
|
||||||
|
|
||||||
interrupt = false;
|
interrupt = false;
|
||||||
sut.Drive(driver);
|
sut.Drive(driver);
|
||||||
@ -234,47 +243,33 @@ TEST(SimpleTaskQueue, PushWithDelayAndDriveOrderly) {
|
|||||||
EXPECT_EQ(called_immediately, 1);
|
EXPECT_EQ(called_immediately, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimpleTaskQueue, ThrowInDrive) {
|
|
||||||
nf7::test::SimpleTaskQueueMock<> sut;
|
|
||||||
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
|
||||||
EXPECT_CALL(sut, onErrorWhileExec).Times(1);
|
|
||||||
|
|
||||||
auto interrupt = false;
|
|
||||||
::testing::NiceMock<nf7::test::SimpleTaskQueueDriverMock<>> driver;
|
|
||||||
ON_CALL(driver, EndBusy)
|
|
||||||
.WillByDefault([&]() { interrupt = true; });
|
|
||||||
ON_CALL(driver, nextIdleInterruption)
|
|
||||||
.WillByDefault([&]() { return interrupt; });
|
|
||||||
|
|
||||||
auto called = uint32_t {0};
|
|
||||||
sut.Push(nf7::Task<> {[&](){ throw nf7::Exception {"helloworld"}; }});
|
|
||||||
sut.Push(nf7::Task<> {[&](){ ++called; }});
|
|
||||||
sut.Drive(driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SimpleTaskQueue, ChaoticPushAndDrive) {
|
TEST(SimpleTaskQueue, ChaoticPushAndDrive) {
|
||||||
constexpr auto kThreads = uint32_t {32};
|
constexpr auto kThreads = uint32_t {32};
|
||||||
constexpr auto kPushPerThread = uint32_t {100};
|
constexpr auto kPushPerThread = uint32_t {100};
|
||||||
|
|
||||||
std::vector<uint32_t> values(kThreads);
|
nf7::test::SimpleTaskQueueMock<nf7::Task<int32_t&>> sut;
|
||||||
std::vector<std::thread> threads(kThreads);
|
|
||||||
std::atomic<uint32_t> exitedThreads {0};
|
|
||||||
|
|
||||||
nf7::test::SimpleTaskQueueMock<> sut;
|
|
||||||
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
EXPECT_CALL(sut, onErrorWhilePush).Times(0);
|
||||||
EXPECT_CALL(sut, onErrorWhileExec).Times(0);
|
|
||||||
|
|
||||||
// use NiceMock to suppress annoying warnings that slowed unittests
|
// use NiceMock to suppress annoying warnings that slowed unittests
|
||||||
::testing::NiceMock<nf7::test::SimpleTaskQueueDriverMock<>> driver;
|
::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)
|
ON_CALL(driver, nextIdleInterruption)
|
||||||
.WillByDefault([&]() { return exitedThreads >= kThreads; });
|
.WillByDefault([&]() { return exitedThreads >= kThreads; });
|
||||||
|
|
||||||
for (uint32_t i = 0; i < kThreads; ++i) {
|
for (uint32_t i = 0; i < kThreads; ++i) {
|
||||||
threads[i] = std::thread {[&, i](){
|
threads[i] = std::thread {[&, i](){
|
||||||
for (uint32_t j = 0; j < kPushPerThread; ++j) {
|
for (uint32_t j = 0; j < kPushPerThread; ++j) {
|
||||||
sut.Push(nf7::Task<> {[&, i](){ ++values[i]; }});
|
sut.Exec([&, i](auto&){ ++values[i]; });
|
||||||
}
|
}
|
||||||
sut.Push(nf7::Task<> {[&](){ ++exitedThreads; }});
|
sut.Exec([&](auto&){ ++exitedThreads; });
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
for (auto& th : threads) {
|
for (auto& th : threads) {
|
||||||
|
@ -11,39 +11,43 @@
|
|||||||
|
|
||||||
namespace nf7::test {
|
namespace nf7::test {
|
||||||
|
|
||||||
template <typename... Args>
|
template <TaskLike T>
|
||||||
class TaskQueueMock : public TaskQueue<Args...> {
|
class TaskQueueMock : public TaskQueue<T> {
|
||||||
public:
|
public:
|
||||||
using Item = Task<Args...>;
|
using Item = T;
|
||||||
|
|
||||||
TaskQueueMock() = default;
|
TaskQueueMock() = default;
|
||||||
|
|
||||||
MOCK_METHOD(void, Push, (Item&&), (noexcept, override));
|
MOCK_METHOD(void, Push, (Item&&), (noexcept, override));
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Args>
|
template <TaskLike T>
|
||||||
class SimpleTaskQueueMock : public SimpleTaskQueue<Args...> {
|
class SimpleTaskQueueMock : public SimpleTaskQueue<T> {
|
||||||
public:
|
public:
|
||||||
SimpleTaskQueueMock() = default;
|
SimpleTaskQueueMock() = default;
|
||||||
|
|
||||||
MOCK_METHOD(void, onErrorWhilePush, (std::source_location), (noexcept));
|
MOCK_METHOD(void, onErrorWhilePush, (std::source_location), (noexcept));
|
||||||
MOCK_METHOD(void, onErrorWhileExec, (std::source_location), ());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Args>
|
template <TaskLike T>
|
||||||
class SimpleTaskQueueDriverMock : public SimpleTaskQueue<Args...>::Driver {
|
class SimpleTaskQueueDriverMock {
|
||||||
public:
|
public:
|
||||||
using Item = Task<Args...>;
|
using Item = T;
|
||||||
using Time = typename Item::Time;
|
using Time = typename Item::Time;
|
||||||
|
|
||||||
SimpleTaskQueueDriverMock() = default;
|
SimpleTaskQueueDriverMock() = default;
|
||||||
|
|
||||||
MOCK_METHOD(void, BeginBusy, (), (noexcept, override));
|
SimpleTaskQueueDriverMock(const SimpleTaskQueueDriverMock&) = delete;
|
||||||
MOCK_METHOD(void, EndBusy, (), (noexcept, override));
|
SimpleTaskQueueDriverMock(SimpleTaskQueueDriverMock&&) = delete;
|
||||||
MOCK_METHOD(Time, tick, (), (const, noexcept, override));
|
SimpleTaskQueueDriverMock& operator=(const SimpleTaskQueueDriverMock&) = delete;
|
||||||
MOCK_METHOD(std::tuple<Args...>, params, (), (const, noexcept, override));
|
SimpleTaskQueueDriverMock& operator=(SimpleTaskQueueDriverMock&&) = delete;
|
||||||
MOCK_METHOD(bool, nextIdleInterruption, (), (const, override, noexcept));
|
|
||||||
MOCK_METHOD(bool, nextTaskInterruption, (), (const, override, noexcept));
|
MOCK_METHOD(void, BeginBusy, (), (noexcept));
|
||||||
|
MOCK_METHOD(void, Drive, (T&&), (noexcept));
|
||||||
|
MOCK_METHOD(void, EndBusy, (), (noexcept));
|
||||||
|
MOCK_METHOD(Time, tick, (), (const, noexcept));
|
||||||
|
MOCK_METHOD(bool, nextIdleInterruption, (), (const, noexcept));
|
||||||
|
MOCK_METHOD(bool, nextTaskInterruption, (), (const, noexcept));
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nf7::test
|
} // namespace nf7::test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user