Compare commits
No commits in common. "5b6f20a70a11109046028abe989cd0b340e4e646" and "d7cfa3f76c81750d80f5998a4b00bab4e01f053b" have entirely different histories.
5b6f20a70a
...
d7cfa3f76c
@ -11,7 +11,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
# Core library
|
# Core library
|
||||||
add_library(core
|
add_library(core
|
||||||
src/core/SysfsRead.cxx
|
src/core/SysfsRead.cxx
|
||||||
src/core/Producer.cxx
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|||||||
@ -21,7 +21,3 @@ 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.
|
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.
|
**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).
|
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
// Producer.hpp
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Author: Unai Blazquez <unaibg2000@gmail.com>
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <functional>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "SysfsRead.hpp"
|
|
||||||
|
|
||||||
using RandomFn = std::function<int()>;
|
|
||||||
using LogFn = std::function<void(const std::string&)>;
|
|
||||||
using SleepFn = std::function<void(std::chrono::milliseconds)>;
|
|
||||||
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<void(int)> 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<bool> 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<void(int)> m_send;
|
|
||||||
std::chrono::milliseconds compute_delay(SysfsStatus status) const;
|
|
||||||
};
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
// Producer.cxx
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
// Author: Unai Blazquez <unaibg2000@gmail.com>
|
|
||||||
|
|
||||||
#include "Producer.hpp"
|
|
||||||
|
|
||||||
#include "SysfsRead.hpp"
|
|
||||||
|
|
||||||
Producer::Producer(const std::filesystem::path& sysfs_path,
|
|
||||||
std::function<void(int)> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,16 +12,4 @@ target_link_libraries(test_sysfs_reader
|
|||||||
gtest_main
|
gtest_main
|
||||||
)
|
)
|
||||||
add_test(NAME test_sysfs_reader COMMAND test_sysfs_reader)
|
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)
|
|
||||||
|
|
||||||
|
|||||||
@ -1,130 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#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<int> outputs;
|
|
||||||
std::vector<std::string> 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); },
|
|
||||||
[](std::chrono::milliseconds) {}};
|
|
||||||
// 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);
|
|
||||||
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<int> outputs;
|
|
||||||
std::vector<std::string> 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<int> outputs;
|
|
||||||
std::vector<std::string> 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();
|
|
||||||
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<int> outputs;
|
|
||||||
std::vector<std::string> 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<int> outputs;
|
|
||||||
std::vector<std::string> 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);
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user