#include "catch.hpp"
#include <chrono>
#include <thread>
#include "Blackboard.h"
#include "Task.h"
#include "TaskGroupSelector.h"
#include "TaskGroupSequence.h"
#include "TaskScheduler.h"
namespace
{
void executeTask(Task& task)
{
std::shared_ptr<Blackboard> blackboard = std::make_shared<Blackboard>();
while (true)
{
if (task.update(blackboard) != Task::STATE_RUNNING)
{
return;
}
}
}
class TestTask: public Task
{
public:
TestTask(int* orderCountPtr, int updateCount, TaskState returnState = STATE_SUCCESS)
: orderCount(*orderCountPtr)
, updateCount(updateCount)
, returnState(returnState)
, enterCallOrder(0)
, updateCallOrder(0)
, exitCallOrder(0)
, resetCallOrder(0)
{
}
virtual void doEnter(std::shared_ptr<Blackboard> blackboard)
{
enterCallOrder = ++orderCount;
}
virtual TaskState doUpdate(std::shared_ptr<Blackboard> blackboard)
{
updateCallOrder = ++orderCount;
if (updateCount < 0)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return Task::STATE_RUNNING;
}
updateCount--;
if (updateCount)
{
return Task::STATE_RUNNING;
}
return returnState;
}
virtual void doExit(std::shared_ptr<Blackboard> blackboard)
{
exitCallOrder = ++orderCount;
}
virtual void doReset(std::shared_ptr<Blackboard> blackboard)
{
resetCallOrder = ++orderCount;
}
int& orderCount;
int updateCount;
TaskState returnState;
int enterCallOrder;
int updateCallOrder;
int exitCallOrder;
int resetCallOrder;
};
class TestTaskDispatch: public TestTask
{
public:
TestTaskDispatch(int* orderCountPtr, int updateCount, TaskScheduler* scheduler)
: TestTask(orderCountPtr, updateCount), scheduler(scheduler)
{
}
virtual TaskState doUpdate(std::shared_ptr<Blackboard> blackboard)
{
subTask = std::make_shared<TestTask>(&orderCount, 1);
scheduler->pushTask(subTask);
return TestTask::doUpdate(blackboard);
}
TaskScheduler* scheduler;
std::shared_ptr<TestTask> subTask;
};
void waitForThread(TaskScheduler& scheduler)
{
static const int THREAD_WAIT_TIME_MS = 20;
do
{
std::this_thread::sleep_for(std::chrono::milliseconds(THREAD_WAIT_TIME_MS));
} while (scheduler.hasTasksQueued());
}
}
TEST_CASE("scheduler loop starts and stops")
{
TaskScheduler scheduler(0);
REQUIRE(!scheduler.loopIsRunning());
scheduler.startSchedulerLoopThreaded();
waitForThread(scheduler);
REQUIRE(scheduler.loopIsRunning());
scheduler.stopSchedulerLoop();
waitForThread(scheduler);
REQUIRE(!scheduler.loopIsRunning());
}
TEST_CASE("tasks get executed without scheduling in correct order")
{
int order = 0;
TestTask task(&order, 1);
executeTask(task);
REQUIRE(3 == order);
REQUIRE(1 == task.enterCallOrder);
REQUIRE(2 == task.updateCallOrder);
REQUIRE(3 == task.exitCallOrder);
}
TEST_CASE("scheduled tasks get processed with callbacks in correct order")
{
TaskScheduler scheduler(0);
scheduler.startSchedulerLoopThreaded();
int order = 0;
std::shared_ptr<TestTask> task = std::make_shared<TestTask>(&order, 1);
scheduler.pushTask(task);
waitForThread(scheduler);
scheduler.stopSchedulerLoop();
REQUIRE(3 == order);
REQUIRE(1 == task->enterCallOrder);
REQUIRE(2 == task->updateCallOrder);
REQUIRE(3 == task->exitCallOrder);
}
TEST_CASE("sequential task group to process tasks in correct order")
{
TaskScheduler scheduler(0);
scheduler.startSchedulerLoopThreaded();
int order = 0;
std::shared_ptr<TestTask> task1 = std::make_shared<TestTask>(&order, 1);
std::shared_ptr<TestTask> task2 = std::make_shared<TestTask>(&order, 1);
std::shared_ptr<TaskGroupSequence> taskGroup = std::make_shared<TaskGroupSequence>();
taskGroup->addTask(task1);
taskGroup->addTask(task2);
scheduler.pushTask(taskGroup);
waitForThread(scheduler);
scheduler.stopSchedulerLoop();
REQUIRE(6 == order);
REQUIRE(1 == task1->enterCallOrder);
REQUIRE(2 == task1->updateCallOrder);
REQUIRE(3 == task1->exitCallOrder);
REQUIRE(4 == task2->enterCallOrder);
REQUIRE(5 == task2->updateCallOrder);
REQUIRE(6 == task2->exitCallOrder);
}
TEST_CASE("sequential task group does not evaluate tasks after failure")
{
TaskScheduler scheduler(0);
scheduler.startSchedulerLoopThreaded();
int order = 0;
std::shared_ptr<TestTask> task1 = std::make_shared<TestTask>(&order, 1, Task::STATE_FAILURE);
std::shared_ptr<TestTask> task2 = std::make_shared<TestTask>(&order, -1);
std::shared_ptr<TaskGroupSequence> taskGroup = std::make_shared<TaskGroupSequence>();
taskGroup->addTask(task1);
taskGroup->addTask(task2);
scheduler.pushTask(taskGroup);
waitForThread(scheduler);
scheduler.stopSchedulerLoop();
REQUIRE(1 == task1->enterCallOrder);
REQUIRE(2 == task1->updateCallOrder);
REQUIRE(3 == task1->exitCallOrder);
REQUIRE(0 == task2->enterCallOrder);
REQUIRE(0 == task2->updateCallOrder);
REQUIRE(0 == task2->exitCallOrder);
}
TEST_CASE("sequential task group does not evaluate tasks after success")
{
TaskScheduler scheduler(0);
scheduler.startSchedulerLoopThreaded();
int order = 0;
std::shared_ptr<TestTask> task1 = std::make_shared<TestTask>(&order, 1, Task::STATE_FAILURE);
std::shared_ptr<TestTask> task2 = std::make_shared<TestTask>(&order, 1, Task::STATE_SUCCESS);
std::shared_ptr<TestTask> task3 = std::make_shared<TestTask>(&order, -1);
std::shared_ptr<TaskGroupSelector> taskGroup = std::make_shared<TaskGroupSelector>();
taskGroup->addTask(task1);
taskGroup->addTask(task2);
taskGroup->addTask(task3);
scheduler.pushTask(taskGroup);
waitForThread(scheduler);
scheduler.stopSchedulerLoop();
REQUIRE(1 == task1->enterCallOrder);
REQUIRE(2 == task1->updateCallOrder);
REQUIRE(3 == task1->exitCallOrder);
REQUIRE(4 == task2->enterCallOrder);
REQUIRE(5 == task2->updateCallOrder);
REQUIRE(6 == task2->exitCallOrder);
REQUIRE(0 == task3->enterCallOrder);
REQUIRE(0 == task3->updateCallOrder);
REQUIRE(0 == task3->exitCallOrder);
}
TEST_CASE("task scheduling within task processing")
{
TaskScheduler scheduler(0);
scheduler.startSchedulerLoopThreaded();
int order = 0;
std::shared_ptr<TestTaskDispatch> task = std::make_shared<TestTaskDispatch>(
&order, 1, &scheduler);
scheduler.pushTask(task);
waitForThread(scheduler);
scheduler.stopSchedulerLoop();
REQUIRE(6 == order);
REQUIRE(1 == task->enterCallOrder);
REQUIRE(2 == task->updateCallOrder);
REQUIRE(3 == task->exitCallOrder);
REQUIRE(4 == task->subTask->enterCallOrder);
REQUIRE(5 == task->subTask->updateCallOrder);
REQUIRE(6 == task->subTask->exitCallOrder);
}