Files

277 lines
8.9 KiB
C++

#include "doctest.h"
#include "fl/task.h"
#include "fl/async.h"
#include "fl/engine_events.h"
#include "fl/time.h"
using namespace fl;
namespace {
// Helper class to track frame events for testing
class TestFrameListener : public fl::EngineEvents::Listener {
public:
TestFrameListener() {
fl::EngineEvents::addListener(this);
}
~TestFrameListener() {
fl::EngineEvents::removeListener(this);
}
void onEndFrame() override {
frame_count++;
// Pump the scheduler to execute after_frame tasks specifically
fl::Scheduler::instance().update_after_frame_tasks();
}
int frame_count = 0;
};
} // anonymous namespace
TEST_CASE("Task self-registration and destruction behavior [task]") {
SUBCASE("Task auto-registers when callback is set - SUCCESS") {
// Clear any leftover tasks from previous tests
fl::Scheduler::instance().clear_all_tasks();
bool task_executed = false;
// Create task without manually adding to scheduler
// Auto-registration happens when .then() is called
{
fl::task::after_frame()
.then([&task_executed]() {
task_executed = true;
});
// Task temporary object destructs here, but it's already registered
}
// Simulate frame end event
TestFrameListener listener;
fl::EngineEvents::onEndFrame();
// Task should now execute due to auto-registration
CHECK(task_executed);
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
SUBCASE("Fluent API pattern works with auto-registration") {
// Clear any leftover tasks from previous tests
fl::Scheduler::instance().clear_all_tasks();
bool task_executed = false;
// This fluent pattern should now work correctly
fl::task::after_frame().then([&task_executed]() {
task_executed = true;
});
// Entire chain destructs here, but task was auto-registered
// Simulate frame end event
TestFrameListener listener;
fl::EngineEvents::onEndFrame();
// Task should execute
CHECK(task_executed);
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
SUBCASE("Multiple auto-registering tasks work correctly") {
// Clear any leftover tasks from previous tests
fl::Scheduler::instance().clear_all_tasks();
int tasks_executed = 0;
// Create multiple tasks without saving them - they auto-register
for (int i = 0; i < 3; i++) {
fl::task::after_frame()
.then([&tasks_executed]() {
tasks_executed++;
});
// Each task auto-registers when .then() is called
}
// Simulate frame end event
TestFrameListener listener;
fl::EngineEvents::onEndFrame();
// All 3 tasks should execute
CHECK_EQ(tasks_executed, 3);
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
SUBCASE("Manual registration still works (backward compatibility)") {
// Clear any leftover tasks from previous tests
fl::Scheduler::instance().clear_all_tasks();
bool task_executed = false;
// Old style should still work
auto task = fl::task::after_frame()
.then([&task_executed]() {
task_executed = true;
});
// Manual add should work (though now redundant since auto-registration already happened)
fl::Scheduler::instance().add_task(task);
// Simulate frame end event
TestFrameListener listener;
fl::EngineEvents::onEndFrame();
// Task should execute (only once, not twice)
CHECK(task_executed);
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
SUBCASE("Task cancellation works with auto-registered tasks") {
// Clear any leftover tasks from previous tests
fl::Scheduler::instance().clear_all_tasks();
bool task_executed = false;
// Create auto-registering task and save reference for cancellation
auto task = fl::task::after_frame()
.then([&task_executed]() {
task_executed = true;
});
// Task auto-registered when .then() was called
// Cancel the task
task.cancel();
// Simulate frame end event
TestFrameListener listener;
fl::EngineEvents::onEndFrame();
// Task should NOT execute due to cancellation
CHECK_FALSE(task_executed);
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
SUBCASE("Tasks without callbacks don't auto-register") {
// Clear any leftover tasks from previous tests
fl::Scheduler::instance().clear_all_tasks();
// Create task without callback - should not auto-register
auto task = fl::task::after_frame();
CHECK_FALSE(task.has_then());
CHECK(task.is_valid()); // Task should be valid but not auto-registered
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
SUBCASE("every_ms task runs immediately once then respects timing interval") {
// Clear any leftover tasks from previous tests
fl::Scheduler::instance().clear_all_tasks();
int execution_count = 0;
// Create a task that runs every 100ms and auto-registers
auto task = fl::task::every_ms(100)
.then([&execution_count]() {
execution_count++;
});
// Task should be auto-registered and ready to run immediately
CHECK(task.is_valid());
CHECK(task.has_then());
// First update - should run immediately
fl::Scheduler::instance().update();
CHECK_EQ(execution_count, 1);
// Immediate second update - should NOT run (not enough time passed)
fl::Scheduler::instance().update();
CHECK_EQ(execution_count, 1); // Still 1, didn't run again
// Manually advance the task's last run time to simulate 50ms passing
uint32_t current_time = fl::time();
task.set_last_run_time(current_time - 50);
// Update - should still NOT run (only 50ms passed, need 100ms)
fl::Scheduler::instance().update();
CHECK_EQ(execution_count, 1); // Still 1
// Manually advance to simulate 100ms+ passing
task.set_last_run_time(current_time - 100);
// Update - should run now (100ms has passed)
fl::Scheduler::instance().update();
CHECK_EQ(execution_count, 2); // Should be 2 now
// Immediate update again - should NOT run
fl::Scheduler::instance().update();
CHECK_EQ(execution_count, 2); // Still 2
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
SUBCASE("after_frame task executes when FastLED.show() is called") {
// Clear any leftover tasks from previous tests - CRITICAL for test isolation
fl::Scheduler::instance().clear_all_tasks();
int execution_count = 0;
// Create an after_frame task that auto-registers
auto task = fl::task::after_frame()
.then([&execution_count]() {
execution_count++;
});
// Task should be auto-registered
CHECK(task.is_valid());
CHECK(task.has_then());
// Initial state - task hasn't run yet
CHECK_EQ(execution_count, 0);
// Manually calling scheduler update shouldn't trigger frame tasks yet
fl::Scheduler::instance().update();
CHECK_EQ(execution_count, 0); // Still 0
// Create a frame listener for this specific test
TestFrameListener listener;
// Instead of calling FastLED.show(), directly trigger the engine events
// This tests the task system without requiring LED hardware setup
fl::EngineEvents::onEndFrame();
// The after_frame task should have executed
CHECK_EQ(execution_count, 1);
// Calling onEndFrame() again should execute the task again (it's been removed as one-shot)
// But since it's already removed, create another task to test
auto task2 = fl::task::after_frame()
.then([&execution_count]() {
execution_count++;
});
fl::EngineEvents::onEndFrame();
CHECK_EQ(execution_count, 2);
// Clean up
fl::Scheduler::instance().clear_all_tasks();
}
}