diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a55ac0..891bfc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Core library add_library(core src/core/SysfsRead.cxx + src/core/Producer.cxx ) target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/README.md b/README.md index 8c289fb..f334dd7 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,7 @@ A Test-Driven Development (TDD) workflow was followed throughout the project. Ev The reader never throws on I/O errors; every outcome is expressed through the enum so callers can react without exception handling. A helper `trim_in_place` strips trailing whitespace and newlines before comparison. **Tests:** [tests/test_sysfs_read.cxx](tests/test_sysfs_read.cxx) — covers all five status branches by writing controlled content to a temporary file. + +## Producer class / thread + +`Producer` ([include/Producer.hpp](include/Producer.hpp), [src/core/Producer.cxx](src/core/Producer.cxx)) runs a worker `std::thread` that periodically polls the `SysfsReader` and, when the status is `Enabled`, generates a random integer and forwards it through an injected `send_fn` callback. The polling interval is 1 second under normal conditions and 7 seconds when the sysfs file reports `ErrorTempTooHigh` (cool-down). diff --git a/include/Producer.hpp b/include/Producer.hpp new file mode 100644 index 0000000..d21ba85 --- /dev/null +++ b/include/Producer.hpp @@ -0,0 +1,48 @@ +#pragma once +// Producer.hpp +// SPDX-License-Identifier: GPL-3.0-or-later +// Author: Unai Blazquez +#include +#include +#include +#include +#include + +#include "SysfsRead.hpp" + +using RandomFn = std::function; +using LogFn = 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. + Producer(const std::filesystem::path& sysfs_path, + std::function send_fn, RandomFn random_fn, + LogFn log_fn = nullptr, 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); + } + LogFn m_log; + std::function m_send; + std::chrono::milliseconds compute_delay(SysfsStatus status) const; +}; diff --git a/src/core/Producer.cxx b/src/core/Producer.cxx new file mode 100644 index 0000000..8833d8f --- /dev/null +++ b/src/core/Producer.cxx @@ -0,0 +1,84 @@ +// 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, + LogFn log_fn, SleepFn sleep_fn) + : m_reader(sysfs_path), + m_send(std::move(send_fn)), + m_random(std::move(random_fn)), + m_log(std::move(log_fn)), + m_sleep(std::move(sleep_fn)) +{ +} +std::chrono::milliseconds Producer::compute_delay(SysfsStatus status) const +{ + using namespace std::chrono_literals; + + auto standard = 1000ms; // example: 1s, must be < 7s + auto hot = 7000ms; // exactly 7s + + if (status == SysfsStatus::ErrorTempTooHigh) + { // when error = temp too high + return hot; + } + else + { + return standard; + } +} + +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() +{ + while (m_running.load()) + { + auto status = m_reader.read_status(); + switch (status) + { + case SysfsStatus::Enabled: + m_send(m_random()); + if (m_log) m_log("Producer: Enabled"); + break; + + case SysfsStatus::Unreachable: + // do nothing for now + if (m_log) m_log("Producer: SysfsFile Unreachable"); + break; + + case SysfsStatus::Empty: + if (m_log) m_log("Producer: SysfsFile Empty"); + break; + + case SysfsStatus::ErrorTempTooHigh: + if (m_log) m_log("Producer: Error temp too high!!"); + break; + + case SysfsStatus::UnexpectedValue: + if (m_log) m_log("Producer: UnexpectedValue"); + break; + } + auto delay = compute_delay(status); + m_sleep(delay); + // Thread will end here (for now) stop will join it + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae62cc1..82e7429 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,4 +12,16 @@ target_link_libraries(test_sysfs_reader gtest_main ) add_test(NAME test_sysfs_reader COMMAND test_sysfs_reader) +add_executable(test_producer + test_producer.cxx +) + +target_link_libraries(test_producer + PRIVATE + core + gtest + gtest_main +) + +add_test(NAME test_producer COMMAND test_producer) diff --git a/tests/test_producer.cxx b/tests/test_producer.cxx new file mode 100644 index 0000000..0c84f14 --- /dev/null +++ b/tests/test_producer.cxx @@ -0,0 +1,124 @@ +#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; + std::vector logs; + + // construct a producer with fake file and callback + Producer producer{"fake_sysfs_input", [&outputs](int value) + { outputs.push_back(value); }, []() { return 42; }, + [&logs](const std::string& msg) { logs.push_back(msg); }}; + // Act: initialize producer and stop it. + producer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + producer.stop(); + + // Assert: we expect one output being 42 + EXPECT_EQ(outputs[0], 42); + EXPECT_NE(logs[0].find("Enabled"), std::string::npos); +} + +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; + std::vector logs; + + Producer producer{"fake_sysfs_input", [&outputs](int value) + { outputs.push_back(value); }, []() { return 42; }, + [&logs](const std::string& msg) { logs.push_back(msg); }, + [](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()); + EXPECT_NE(logs[0].find("UnexpectedValue"), std::string::npos); +} + +TEST(ProducerTest, ProducerDoesNotCallWhenEmpty) +{ + // Arrange create the file and write "0\n" into it. + { + std::ofstream out("fake_sysfs_input"); + out << " "; + } + + std::vector outputs; + std::vector logs; + + Producer producer{"fake_sysfs_input", [&outputs](int value) + { outputs.push_back(value); }, []() { return 42; }, + [&logs](const std::string& msg) { logs.push_back(msg); }, + [](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()); + EXPECT_NE(logs[0].find("Empty"), std::string::npos); +} + +TEST(ProducerTest, ProducerDoesNotCallWhenUnreachable) +{ + std::vector outputs; + std::vector logs; + + Producer producer{"nonexistant_sysfs_input", [&outputs](int value) + { outputs.push_back(value); }, []() { return 42; }, + [&logs](const std::string& msg) { logs.push_back(msg); }, + [](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()); + EXPECT_NE(logs[0].find("Unreachable"), std::string::npos); +} + +TEST(ProducerTest, ProducerDoesNotCallWhenTempTooHigh) +{ + // create a file that contains error + { + std::ofstream out("fake_sysfs_input"); + out << "error: temp too high"; + } + std::vector outputs; + std::vector logs; + + Producer producer{"fake_sysfs_input", [&outputs](int value) + { outputs.push_back(value); }, []() { return 42; }, + [&logs](const std::string& msg) { logs.push_back(msg); }, + [](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()); + EXPECT_NE(logs[0].find("Error"), std::string::npos); +}