Skip to content

Commit

Permalink
Working start and join rules; random test
Browse files Browse the repository at this point in the history
  • Loading branch information
LeStarch committed Apr 11, 2024
1 parent 97fe724 commit eabfb6f
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 28 deletions.
117 changes: 112 additions & 5 deletions Os/test/ut/task/CommonTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,82 @@
// ======================================================================
#include <gtest/gtest.h>
#include "Os/Task.hpp"
#include "Os/test/ut/task/CommonTests.hpp"
#include "Os/test/ut/task/RulesHeaders.hpp"

static const U32 RANDOM_BOUND = 1000;

namespace Os {
namespace Test {
namespace Task {
void run_test(void *argument) {
RunNotification &run_notification = *reinterpret_cast<RunNotification *>(argument);
run_notification.m_lock.lock();
run_notification.m_notification = true;
run_notification.m_lock.unlock();

TestTaskInfo::~TestTaskInfo() {
this->stop();
}

void TestTaskInfo::step() {
this->m_lock.lock();
// Read update and clear signal
if (this->m_signal) {
this->m_stage = (this->m_stage == Lifecycle::END) ? Lifecycle::END : static_cast<Lifecycle>(this->m_stage + 1);
}
this->m_signal = false;
this->m_lock.unlock();
}

void TestTaskInfo::signal() {
// Block waiting for m_signal to be false
bool loop = true;
while (loop) {
this->m_lock.lock();
// When signal is low, stop looping and make signal high
if (not this->m_signal) {
loop = false;
this->m_signal = true;
}
this->m_lock.unlock();
}
}

TestTaskInfo::Lifecycle TestTaskInfo::stage() {
Lifecycle stage = Lifecycle::UNSET;
this->m_lock.lock();
stage = this->m_stage;
this->m_lock.unlock();
return stage;
}

void TestTaskInfo::stop() {
while (this->stage() != Lifecycle::END) {
this->signal();
}
this->m_task.join();
}

void TestTaskInfo::standard_task(void *argument) {
ASSERT_NE(argument, nullptr) << "Test provided bad argument pointer";
TestTaskInfo& task_info = *reinterpret_cast<TestTaskInfo*>(argument);

// Loop until the lifecycle is at the end
while (task_info.stage() != TestTaskInfo::Lifecycle::END) {
task_info.step();
}
}

void TestTaskInfo::joining_task(void *argument) {
ASSERT_NE(argument, nullptr) << "Test provided bad argument pointer";
TestTaskInfo& task_info = *reinterpret_cast<TestTaskInfo*>(argument);
ASSERT_NE(task_info.m_other, nullptr) << "Other pointer not properly set";

// Loop until the lifecycle is at the MIDDLE
while (task_info.stage() != TestTaskInfo::Lifecycle::MIDDLE) {
task_info.step();
}
// Signal will come from test thread then this will join
task_info.m_other->m_task.join();
task_info.m_lock.lock();
task_info.m_stage = TestTaskInfo::Lifecycle::END;
task_info.m_lock.unlock();
}
}
}
Expand All @@ -26,4 +92,45 @@ TEST(Functionality, StartTask) {
rule.apply(tester);
}

// Ensure that open mode changes work reliably
TEST(Functionality, StartJoinTask) {
Os::Test::Task::Tester tester;
Os::Test::Task::Tester::Start start_rule;
Os::Test::Task::Tester::Join join_rule;
start_rule.apply(tester);
join_rule.apply(tester);

}


// Ensure a write followed by a read produces valid data
TEST(Functionality, RandomizedTesting) {
Os::Test::Task::Tester tester;
// Enumerate all rules and construct an instance of each
Os::Test::Task::Tester::Start start_rule;
Os::Test::Task::Tester::Join join_rule;


// Place these rules into a list of rules
STest::Rule<Os::Test::Task::Tester>* rules[] = {
&start_rule,
&join_rule,
};

// Take the rules and place them into a random scenario
STest::RandomScenario<Os::Test::Task::Tester> random(
"Random Rules",
rules,
FW_NUM_ARRAY_ELEMENTS(rules)
);

// Create a bounded scenario wrapping the random scenario
STest::BoundedScenario<Os::Test::Task::Tester> bounded(
"Bounded Random Rules Scenario",
random,
RANDOM_BOUND
);
// Run!
const U32 numSteps = bounded.run(tester);
printf("Ran %u steps.\n", numSteps);
}
7 changes: 7 additions & 0 deletions Os/test/ut/task/CommonTests.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//
// Created by Michael Starch on 4/11/24.
//
#include "RulesHeaders.hpp"
#ifndef OS_TEST_UT_TASK_COMMON_TESTS_HPP
#define OS_TEST_UT_TASK_COMMON_TESTS_HPP
#endif //OS_TEST_UT_TASK_COMMON_TESTS_HPP
49 changes: 42 additions & 7 deletions Os/test/ut/task/RulesHeaders.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,58 @@
#include "STest/Scenario/Scenario.hpp"
#include "Os/Task.hpp"
#include "Os/Mutex.hpp"
#include <vector>

namespace Os {
namespace Test {
namespace Task {

struct RunNotification {


struct TestTaskInfo {
//! Test task life-cycle stages
enum Lifecycle {
BEGINNING = 0, //!< The initial stage of the task
MIDDLE = 1, //!< The task function has taken hold
END = 2, //!< The task has been asked to exit
UNSET = -1,
JOINED = -2, //!< Joining task has successfully joined
};

Lifecycle m_stage = Lifecycle::BEGINNING;
Os::Mutex m_lock;
bool m_notification = false;
};
Os::Task::State m_state = Os::Task::State::NOT_STARTED; //!< Shadow state of the task
Os::Task m_task; //!< Task under test
std::shared_ptr<TestTaskInfo> m_other = nullptr;
bool m_signal = false;

~TestTaskInfo();

//! Atomically step through lifecycle stages
void step();

//! Signal a step through lifecycle stage
void signal();

void run_test(void* argument);
//! Get lifecycle stage
Lifecycle stage();

//! Stop and join thread
void stop();


//! Standard task implementation which waits at each lifecycle stage
static void standard_task(void* argument);

//! Joining task implementation which waits at each lifecycle stage
static void joining_task(void* argument);
};

struct Tester {
private:
Os::Task::State m_state = Os::Task::State::NOT_STARTED;
Os::Task m_task;
RunNotification m_notification;
static constexpr FwSizeType MAX_THREAD_COUNT = 100;

std::vector<std::shared_ptr<TestTaskInfo>> m_tasks;

public:
#include "TaskRules.hpp"
Expand Down
100 changes: 84 additions & 16 deletions Os/test/ut/task/TaskRules.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@


#include "RulesHeaders.hpp"
#include "STest/Pick/Pick.hpp"
#include "Fw/Types/String.hpp"


void wait_for_state_with_timeout(Os::Test::Task::TestTaskInfo& info, const Os::Test::Task::TestTaskInfo::Lifecycle& stage, const FwSizeType delay_ms) {
// Loop waiting for transition in preparation for join (with timeout)
FwSizeType i = 0;
for (i = 0; i < (delay_ms * 10); i++) {
Os::Task::delay(Fw::Time(0, 100));
if (info.stage() == stage) {
break;
}
// Run cooperative multitasking when necessary
if (info.m_task.isCooperative()) {
// TODO: run
}
}
ASSERT_LT(i, (delay_ms * 10)) << "Task did not run within " << delay_ms << "ms";
ASSERT_EQ(info.stage(), stage) << "Task lifecycle inconsistent";
}

// ------------------------------------------------------------------------------------------------------
// Rule: Start
//
Expand All @@ -16,31 +35,80 @@ Os::Test::Task::Tester::Start::Start() :
bool Os::Test::Task::Tester::Start::precondition(
const Os::Test::Task::Tester &state //!< The test state
) {
return state.m_state == Os::Task::State::NOT_STARTED;
return state.m_tasks.size() < Os::Test::Task::Tester::MAX_THREAD_COUNT;
}


void Os::Test::Task::Tester::Start::action(
Os::Test::Task::Tester &state //!< The test state
) {
printf("--> Rule: Start \n");
TaskString name("StartRuleTask");
Os::Task::Arguments arguments(name, &run_test, &state.m_notification);
state.m_task.start(arguments);

bool done = false;
FwSizeType i = 0;
for (i = 0; i < 100 and not done; i++) {
Os::Task::delay(Fw::Time(0, 1000));
state.m_notification.m_lock.lock();
done = state.m_notification.m_notification;
state.m_notification.m_lock.unlock();
std::shared_ptr<TestTaskInfo> new_task = std::make_shared<TestTaskInfo>();
state.m_tasks.push_back(new_task);

if (state.m_task.isCooperative()) {
// TODO: run
}
}
ASSERT_LT(i, 100) << "Task did not run within 100ms";

Fw::String name("StartRuleTask");
Os::Task::Arguments arguments(name, &TestTaskInfo::standard_task, &(*new_task));
new_task->m_task.start(arguments);
// Ensure it starts in the correct state
ASSERT_EQ(new_task->stage(), TestTaskInfo::Lifecycle::BEGINNING);

// Poke the task into the MIDDLE state
new_task->signal();
wait_for_state_with_timeout(*new_task, TestTaskInfo::MIDDLE, 100);
}





// ------------------------------------------------------------------------------------------------------
// Rule: Join
//
// ------------------------------------------------------------------------------------------------------

Os::Test::Task::Tester::Join::Join() :
STest::Rule<Os::Test::Task::Tester>("Join")
{
}


bool Os::Test::Task::Tester::Join::precondition(
const Os::Test::Task::Tester& state //!< The test state
)
{
return not state.m_tasks.empty();
}


void Os::Test::Task::Tester::Join::action(
Os::Test::Task::Tester& state //!< The test state
)
{
printf("--> Rule: Join\n");
TestTaskInfo joiner_task;
const U32 random_index = STest::Pick::lowerUpper(0, state.m_tasks.size() - 1);

// Pop the middle
std::shared_ptr<TestTaskInfo> other_task = state.m_tasks[random_index];
state.m_tasks.erase(state.m_tasks.begin() + random_index);

// Set the other argument
joiner_task.m_other = other_task;

Fw::String name("JoinTask");
Os::Task::Arguments arguments(name, &TestTaskInfo::joining_task, &joiner_task);
joiner_task.m_task.start(arguments);

// Ensure it starts in the correct state for joining
ASSERT_EQ(other_task->stage(), TestTaskInfo::Lifecycle::MIDDLE);

// Poke the task into the MIDDLE state
joiner_task.signal();
wait_for_state_with_timeout(joiner_task, TestTaskInfo::MIDDLE, 100);

// Make the other task move from MIDDLE state to end state and wait for JOINED state
other_task->signal();
wait_for_state_with_timeout(joiner_task, TestTaskInfo::END, 100);
}
33 changes: 33 additions & 0 deletions Os/test/ut/task/TaskRules.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,37 @@

};





// ------------------------------------------------------------------------------------------------------
// Rule: Join
//
// ------------------------------------------------------------------------------------------------------
struct Join : public STest::Rule<Os::Test::Task::Tester> {

// ----------------------------------------------------------------------
// Construction
// ----------------------------------------------------------------------

//! Constructor
Join();

// ----------------------------------------------------------------------
// Public member functions
// ----------------------------------------------------------------------

//! Precondition
bool precondition(
const Os::Test::Task::Tester& state //!< The test state
);

//! Action
void action(
Os::Test::Task::Tester& state //!< The test state
);

};


0 comments on commit eabfb6f

Please sign in to comment.