diff --git a/include/Producer.hpp b/include/Producer.hpp new file mode 100644 index 0000000..52214bd --- /dev/null +++ b/include/Producer.hpp @@ -0,0 +1,46 @@ +#pragma once +// Producer.hpp +// SPDX-License-Identifier: GPL-3.0-or-later +// Author: Unai Blazquez +#include +#include +#include +#include + +#include "SysfsRead.hpp" + +using RandomFn = std::function; +using SleepFn = std::function; +class Producer +{ + public: + /// @brief Construct a Producer bound to a sysfs-like control file. + /// @param sysfs_path Path to the control file (e.g. "./fake_sysfs_input"). + /// @param send_fn Function called whenever a new integer should be sent. + /// @param sleep_fn Stub for sleeping function, allows reducing sleep on + /// testing + Producer(const std::filesystem::path& sysfs_path, + std::function send_fn, RandomFn random_fn, + SleepFn sleep_fn = default_sleep); + + /// @brief Start the worker thread. Safe to call only once. + void start(); + + /// @brief Request the worker thread to stop and wait for it to finish. + void stop(); + + private: + /// @brief Main loop executed by the worker thread. + void run_loop(); + + std::thread m_thread; + std::atomic m_running; + SysfsReader m_reader; + RandomFn m_random; + SleepFn m_sleep; + static void default_sleep(std::chrono::milliseconds d) + { + std::this_thread::sleep_for(d); + } + std::function m_send; +}; diff --git a/src/core/Producer.cxx b/src/core/Producer.cxx new file mode 100644 index 0000000..708808a --- /dev/null +++ b/src/core/Producer.cxx @@ -0,0 +1,55 @@ +// Producer.cxx +// SPDX-License-Identifier: GPL-3.0-only +// Author: Unai Blazquez + +#include "Producer.hpp" + +#include "SysfsRead.hpp" + +Producer::Producer(const std::filesystem::path& sysfs_path, + std::function send_fn, RandomFn random_fn + : m_reader(sysfs_path), + m_send(std::move(send_fn)), + m_random(std::move(random_fn)), + m_sleep(std::move(sleep_fn)) +{ +} +void Producer::start() +{ + m_running.store(true); + m_thread = std::thread(&Producer::run_loop, this); +} + +void Producer::stop() +{ + m_running.store(false); + if (m_thread.joinable()) + { + m_thread.join(); + } +} +void Producer::run_loop() +{ + auto status = m_reader.read_status(); + switch (status) + { + case SysfsStatus::Enabled: + m_send(m_random()); + break; + + case SysfsStatus::Unreachable: + // do nothing for now + break; + + case SysfsStatus::Empty: + break; + + case SysfsStatus::ErrorTempTooHigh: + break; + + case SysfsStatus::UnexpectedValue: + break; + } + m_sleep(delay); + // Thread will end here (for now) stop will join it +} diff --git a/tests/test_producer.cxx b/tests/test_producer.cxx new file mode 100644 index 0000000..81a33ac --- /dev/null +++ b/tests/test_producer.cxx @@ -0,0 +1,110 @@ +#include + +#include +#include + +#include "Producer.hpp" + +TEST(ProducerTest, ProducerCallsBackWhenEnabled) +{ + // Arrange create the file and write "1\n" into it. + { + std::ofstream out("fake_sysfs_input"); + out << "1\n"; + } + // create a fake callback function + std::vector outputs; + + // construct a producer with fake file and callback + Producer producer{"fake_sysfs_input", [&outputs](int value) + { outputs.push_back(value); }, []() { return 42; }}; + // Act: initialize producer and stop it. + producer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // ← 1ms window + producer.stop(); + + // Assert: we expect one output being 42 + ASSERT_EQ(outputs.size(), 1u); + EXPECT_EQ(outputs[0], 42); +} + +TEST(ProducerTest, ProducerDoesNotCallWhenUnexpectedValue) +{ + // Arrange create the file and write "0\n" into it. + { + std::ofstream out("fake_sysfs_input"); + out << "0\n"; + } + + std::vector outputs; + + Producer producer{"fake_sysfs_input", + [&outputs](int value) { outputs.push_back(value); }, + []() { return 42; }, [](std::chrono::milliseconds) {}}; + + producer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // ← 1ms window + producer.stop(); + + // Assert: we expect no output + EXPECT_TRUE(outputs.empty()); +} + +TEST(ProducerTest, ProducerDoesNotCallWhenEmpty) +{ + // Arrange create the file and write "0\n" into it. + { + std::ofstream out("fake_sysfs_input"); + out << " "; + } + + std::vector outputs; + + Producer producer{"fake_sysfs_input", + [&outputs](int value) { outputs.push_back(value); }, + []() { return 42; }, [](std::chrono::milliseconds) {}}; + + producer.start(); + producer.stop(); + + // Assert: we expect no output + EXPECT_TRUE(outputs.empty()); +} + +TEST(ProducerTest, ProducerDoesNotCallWhenUnreachable) +{ + std::vector outputs; + std::vector logs; + + Producer producer{"nonexistant_sysfs_input", + [&outputs](int value) { outputs.push_back(value); }, + []() { return 42; }, [](std::chrono::milliseconds) {}}; + + producer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // ← 1ms window + producer.stop(); + + // Assert: we expect no output + EXPECT_TRUE(outputs.empty()); +} + +TEST(ProducerTest, ProducerDoesNotCallWhenTempTooHigh) +{ + // create a file that contains error + { + std::ofstream out("fake_sysfs_input"); + out << "error: temp too high"; + } + std::vector outputs; + + Producer producer{"fake_sysfs_input", + [&outputs](int value) { outputs.push_back(value); }, + []() { return 42; }, [](std::chrono::milliseconds) {}}; + + producer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // ← 1ms window + producer.stop(); + + // Assert: we expect no output + EXPECT_TRUE(outputs.empty()); +}