From d1db054d98164d7cb30e24c124ea3a812d1c4868 Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Fri, 24 Apr 2015 17:39:32 +0200 Subject: [PATCH] test: added TaskSchedulerTestSuite --- .../scheduling/TaskGroupSequential.cpp | 6 +- src/lib/utility/scheduling/TaskScheduler.cpp | 6 + src/lib/utility/scheduling/TaskScheduler.h | 1 + src/test/CMakeLists.txt | 1 + src/test/TaskSchedulerTestSuite.h | 338 ++++++++++++++++++ 5 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 src/test/TaskSchedulerTestSuite.h diff --git a/src/lib/utility/scheduling/TaskGroupSequential.cpp b/src/lib/utility/scheduling/TaskGroupSequential.cpp index 34a8a3f3..cb99af75 100644 --- a/src/lib/utility/scheduling/TaskGroupSequential.cpp +++ b/src/lib/utility/scheduling/TaskGroupSequential.cpp @@ -43,11 +43,11 @@ void TaskGroupSequential::exit() void TaskGroupSequential::interrupt() { - if (m_taskIndex > 0 && size_t(m_taskIndex) < m_tasks.size()) + if (m_taskIndex >= 0 && size_t(m_taskIndex) < m_tasks.size()) { for (int i = m_taskIndex; i >= 0; i--) { - m_tasks[m_taskIndex]->process(true); + m_tasks[i]->process(true); } } } @@ -56,6 +56,6 @@ void TaskGroupSequential::revert() { for (int i = m_tasks.size() - 1; i >= 0; i--) { - m_tasks[m_taskIndex]->process(true); + m_tasks[i]->process(true); } } diff --git a/src/lib/utility/scheduling/TaskScheduler.cpp b/src/lib/utility/scheduling/TaskScheduler.cpp index 9b5267ed..c7d65c74 100644 --- a/src/lib/utility/scheduling/TaskScheduler.cpp +++ b/src/lib/utility/scheduling/TaskScheduler.cpp @@ -112,6 +112,12 @@ bool TaskScheduler::loopIsRunning() const return m_loopIsRunning; } +bool TaskScheduler::hasTasksQueued() const +{ + std::lock_guard lock(m_tasksMutex); + return m_tasks.size(); +} + std::shared_ptr TaskScheduler::s_instance; TaskScheduler::TaskScheduler() diff --git a/src/lib/utility/scheduling/TaskScheduler.h b/src/lib/utility/scheduling/TaskScheduler.h index cb373311..c42e4009 100644 --- a/src/lib/utility/scheduling/TaskScheduler.h +++ b/src/lib/utility/scheduling/TaskScheduler.h @@ -23,6 +23,7 @@ public: void stopSchedulerLoop(); bool loopIsRunning() const; + bool hasTasksQueued() const; private: static std::shared_ptr s_instance; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index cad2de90..5f8508cd 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -27,6 +27,7 @@ add_files( SearchIndexTestSuite.h StorageTestSuite.h StorageGraphTestSuite.h + TaskSchedulerTestSuite.h TextAccessTestSuite.h TokenLocationCollectionTestSuite.h UtilityStringTestSuite.h diff --git a/src/test/TaskSchedulerTestSuite.h b/src/test/TaskSchedulerTestSuite.h new file mode 100644 index 00000000..34e54450 --- /dev/null +++ b/src/test/TaskSchedulerTestSuite.h @@ -0,0 +1,338 @@ +#include + +#include +#include + +#include "utility/scheduling/Task.h" +#include "utility/scheduling/TaskGroupSequential.h" +#include "utility/scheduling/TaskScheduler.h" + +class TaskSchedulerTestSuite: public CxxTest::TestSuite +{ +public: + void test_scheduler_loop_starts_and_stops(void) + { + TS_ASSERT(!TaskScheduler::getInstance()->loopIsRunning()); + + TaskScheduler::getInstance()->startSchedulerLoopThreaded(); + + waitForThread(); + + TS_ASSERT(TaskScheduler::getInstance()->loopIsRunning()); + + TaskScheduler::getInstance()->stopSchedulerLoop(); + + waitForThread(); + + TS_ASSERT(!TaskScheduler::getInstance()->loopIsRunning()); + } + + void test_tasks_get_executed_without_scheduling_in_correct_order(void) + { + int order = 0; + TestTask task(&order, 1); + + task.execute(); + + TS_ASSERT_EQUALS(3, order); + + TS_ASSERT_EQUALS(1, task.enterCallOrder); + TS_ASSERT_EQUALS(2, task.updateCallOrder); + TS_ASSERT_EQUALS(3, task.exitCallOrder); + TS_ASSERT_EQUALS(0, task.interruptCallOrder); + TS_ASSERT_EQUALS(0, task.revertCallOrder); + } + + void test_scheduled_tasks_get_processed_with_callbacks_in_correct_order(void) + { + TaskScheduler::getInstance()->startSchedulerLoopThreaded(); + + int order = 0; + std::shared_ptr task = std::make_shared(&order, 1); + + Task::dispatch(task); + + waitForThread(); + + TaskScheduler::getInstance()->stopSchedulerLoop(); + + TS_ASSERT_EQUALS(3, order); + + TS_ASSERT_EQUALS(1, task->enterCallOrder); + TS_ASSERT_EQUALS(2, task->updateCallOrder); + TS_ASSERT_EQUALS(3, task->exitCallOrder); + TS_ASSERT_EQUALS(0, task->interruptCallOrder); + TS_ASSERT_EQUALS(0, task->revertCallOrder); + } + + void test_scheduled_tasks_get_interrupted_with_callbacks_in_correct_order(void) + { + TaskScheduler::getInstance()->startSchedulerLoopThreaded(); + + int order = 0; + std::shared_ptr task = std::make_shared(&order, -1); + + Task::dispatch(task); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + TaskScheduler::getInstance()->interruptCurrentTask(); + + waitForThread(); + + TaskScheduler::getInstance()->stopSchedulerLoop(); + + TS_ASSERT_EQUALS(1, task->enterCallOrder); + TS_ASSERT_EQUALS(order - 2, task->updateCallOrder); + TS_ASSERT_EQUALS(order - 1, task->interruptCallOrder); + TS_ASSERT_EQUALS(order, task->exitCallOrder); + TS_ASSERT_EQUALS(0, task->revertCallOrder); + } + + void test_sequential_task_group_to_process_tasks_in_correct_order(void) + { + TaskScheduler::getInstance()->startSchedulerLoopThreaded(); + + int order = 0; + std::shared_ptr task1 = std::make_shared(&order, 1); + std::shared_ptr task2 = std::make_shared(&order, 1); + + std::shared_ptr taskGroup = std::make_shared(); + taskGroup->addTask(task1); + taskGroup->addTask(task2); + + Task::dispatch(taskGroup); + + waitForThread(); + + TaskScheduler::getInstance()->stopSchedulerLoop(); + + TS_ASSERT_EQUALS(6, order); + + TS_ASSERT_EQUALS(1, task1->enterCallOrder); + TS_ASSERT_EQUALS(2, task1->updateCallOrder); + TS_ASSERT_EQUALS(3, task1->exitCallOrder); + TS_ASSERT_EQUALS(0, task1->interruptCallOrder); + TS_ASSERT_EQUALS(0, task1->revertCallOrder); + + TS_ASSERT_EQUALS(4, task2->enterCallOrder); + TS_ASSERT_EQUALS(5, task2->updateCallOrder); + TS_ASSERT_EQUALS(6, task2->exitCallOrder); + TS_ASSERT_EQUALS(0, task2->interruptCallOrder); + TS_ASSERT_EQUALS(0, task2->revertCallOrder); + } + + void test_sequential_task_group_to_interrupt_and_revert_tasks_in_correct_order(void) + { + TaskScheduler::getInstance()->startSchedulerLoopThreaded(); + + int order = 0; + std::shared_ptr task1 = std::make_shared(&order, 1); + std::shared_ptr task2 = std::make_shared(&order, -1); + + std::shared_ptr taskGroup = std::make_shared(); + taskGroup->addTask(task1); + taskGroup->addTask(task2); + + Task::dispatch(taskGroup); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + TaskScheduler::getInstance()->interruptCurrentTask(); + + waitForThread(); + + TaskScheduler::getInstance()->stopSchedulerLoop(); + + TS_ASSERT_EQUALS(1, task1->enterCallOrder); + TS_ASSERT_EQUALS(2, task1->updateCallOrder); + TS_ASSERT_EQUALS(3, task1->exitCallOrder); + TS_ASSERT_EQUALS(0, task1->interruptCallOrder); + TS_ASSERT_EQUALS(order, task1->revertCallOrder); + + TS_ASSERT_EQUALS(4, task2->enterCallOrder); + TS_ASSERT_EQUALS(order - 3, task2->updateCallOrder); + TS_ASSERT_EQUALS(order - 2, task2->interruptCallOrder); + TS_ASSERT_EQUALS(order - 1, task2->exitCallOrder); + TS_ASSERT_EQUALS(0, task2->revertCallOrder); + } + + void test_sequential_task_group_to_interrupt_and_revert_nested_task_groups_in_correct_order(void) + { + TaskScheduler::getInstance()->startSchedulerLoopThreaded(); + + int order = 0; + std::shared_ptr task1 = std::make_shared(&order, 1); + std::shared_ptr task2 = std::make_shared(&order, 1); + std::shared_ptr task3 = std::make_shared(&order, -1); + std::shared_ptr task4 = std::make_shared(&order, 1); + + std::shared_ptr taskGroup1 = std::make_shared(); + taskGroup1->addTask(task1); + taskGroup1->addTask(task2); + + std::shared_ptr taskGroup2 = std::make_shared(); + taskGroup2->addTask(task3); + taskGroup2->addTask(task4); + + std::shared_ptr taskGroup3 = std::make_shared(); + taskGroup3->addTask(taskGroup1); + taskGroup3->addTask(taskGroup2); + + Task::dispatch(taskGroup3); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + TaskScheduler::getInstance()->interruptCurrentTask(); + + waitForThread(); + + TaskScheduler::getInstance()->stopSchedulerLoop(); + + TS_ASSERT_EQUALS(1, task1->enterCallOrder); + TS_ASSERT_EQUALS(2, task1->updateCallOrder); + TS_ASSERT_EQUALS(3, task1->exitCallOrder); + TS_ASSERT_EQUALS(0, task1->interruptCallOrder); + TS_ASSERT_EQUALS(order, task1->revertCallOrder); + + TS_ASSERT_EQUALS(4, task2->enterCallOrder); + TS_ASSERT_EQUALS(5, task2->updateCallOrder); + TS_ASSERT_EQUALS(6, task2->exitCallOrder); + TS_ASSERT_EQUALS(0, task2->interruptCallOrder); + TS_ASSERT_EQUALS(order - 1, task2->revertCallOrder); + + TS_ASSERT_EQUALS(7, task3->enterCallOrder); + TS_ASSERT_EQUALS(order - 4, task3->updateCallOrder); + TS_ASSERT_EQUALS(order - 3, task3->interruptCallOrder); + TS_ASSERT_EQUALS(order - 2, task3->exitCallOrder); + TS_ASSERT_EQUALS(0, task3->revertCallOrder); + + TS_ASSERT_EQUALS(0, task4->enterCallOrder); + TS_ASSERT_EQUALS(0, task4->updateCallOrder); + TS_ASSERT_EQUALS(0, task4->exitCallOrder); + TS_ASSERT_EQUALS(0, task4->interruptCallOrder); + TS_ASSERT_EQUALS(0, task4->revertCallOrder); + } + + void test_task_scheduling_within_task_processing() + { + TaskScheduler::getInstance()->startSchedulerLoopThreaded(); + + int order = 0; + std::shared_ptr task = std::make_shared(&order, 1); + + Task::dispatch(task); + + waitForThread(); + + TaskScheduler::getInstance()->stopSchedulerLoop(); + + TS_ASSERT_EQUALS(6, order); + + TS_ASSERT_EQUALS(1, task->enterCallOrder); + TS_ASSERT_EQUALS(2, task->updateCallOrder); + TS_ASSERT_EQUALS(3, task->exitCallOrder); + TS_ASSERT_EQUALS(0, task->interruptCallOrder); + TS_ASSERT_EQUALS(0, task->revertCallOrder); + + TS_ASSERT_EQUALS(4, task->subTask->enterCallOrder); + TS_ASSERT_EQUALS(5, task->subTask->updateCallOrder); + TS_ASSERT_EQUALS(6, task->subTask->exitCallOrder); + TS_ASSERT_EQUALS(0, task->subTask->interruptCallOrder); + TS_ASSERT_EQUALS(0, task->subTask->revertCallOrder); + } + +private: + + class TestTask: public Task + { + public: + TestTask(int* orderCountPtr, int updateCount) + : orderCount(*orderCountPtr) + , updateCount(updateCount) + , enterCallOrder(0) + , updateCallOrder(0) + , exitCallOrder(0) + , interruptCallOrder(0) + , revertCallOrder(0) + { + } + + virtual void enter() + { + enterCallOrder = ++orderCount; + } + + virtual TaskState update() + { + 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 Task::STATE_FINISHED; + } + + virtual void exit() + { + exitCallOrder = ++orderCount; + } + + virtual void interrupt() + { + interruptCallOrder = ++orderCount; + } + + virtual void revert() + { + revertCallOrder = ++orderCount; + } + + int& orderCount; + int updateCount; + + int enterCallOrder; + int updateCallOrder; + int exitCallOrder; + int interruptCallOrder; + int revertCallOrder; + }; + + class TestTaskDispatch: public TestTask + { + public: + TestTaskDispatch(int* orderCountPtr, int updateCount) + : TestTask(orderCountPtr, updateCount) + { + } + + virtual TaskState update() + { + subTask = std::make_shared(&orderCount, 1); + Task::dispatch(subTask); + + return TestTask::update(); + } + + std::shared_ptr subTask; + }; + + void waitForThread() const + { + static const int THREAD_WAIT_TIME_MS = 20; + do + { + std::this_thread::sleep_for(std::chrono::milliseconds(THREAD_WAIT_TIME_MS)); + } + while (TaskScheduler::getInstance()->hasTasksQueued()); + } +};