Compare commits
No commits in common. "544d50ee6ad83b44e4d97a67d21472ec1a9ca03d" and "149c3a22b749b6633ad6eea261d4c4824c8a8f3f" have entirely different histories.
544d50ee6a
...
149c3a22b7
@ -11,7 +11,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# Core library
|
||||
add_library(core
|
||||
src/core/SysfsRead.cxx
|
||||
src/core/UnixIpcBridge.cxx
|
||||
src/core/Producer.cxx
|
||||
)
|
||||
|
||||
|
||||
@ -25,12 +25,3 @@ The reader never throws on I/O errors; every outcome is expressed through the en
|
||||
## 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).
|
||||
|
||||
## UnixIpcBridge
|
||||
|
||||
>[!note]
|
||||
>why unix domain sockets? Because I have more experience with them under linux than with posix shared memmory and semaphore, and I find them easier to unit-test.
|
||||
|
||||
`UnixIpcBridge` ([include/UnixIpcBridge.hpp](include/UnixIpcBridge.hpp), [src/core/UnixIpcBridge.cxx](src/core/UnixIpcBridge.cxx)) is a small helper that connects to a UNIX domain socket and sends a single `int` per call. It opens a new connection for each value, which keeps the protocol stateless and simple.
|
||||
|
||||
**Tests:** [tests/test_unix_ipc.cxx](tests/test_unix_ipc.cxx) — spins up a fake socket server, sends values through the bridge, and asserts they arrive correctly.
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
// UnixIpcBridge.hpp
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// Author: Unai Blazquez <unaibg2000@gmail.com>
|
||||
|
||||
#pragma once
|
||||
#include <fcntl.h> // non‑blocking
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h> // close()
|
||||
|
||||
#include <string>
|
||||
|
||||
///@brief Small bridge to allow the producer class to send data over UNIX domain
|
||||
/// sockets
|
||||
class UnixIpcBridge
|
||||
{
|
||||
public:
|
||||
///@brief constructor
|
||||
///@param socket path pointing the socket
|
||||
explicit UnixIpcBridge(const std::string& socket_path);
|
||||
|
||||
///@brief sending function, this goes into the producer
|
||||
///@param integer to send over the socket
|
||||
void send(int value);
|
||||
|
||||
private:
|
||||
std::string m_socket_path;
|
||||
int m_socket_fd = -1;
|
||||
|
||||
void connect_to_consumer();
|
||||
};
|
||||
@ -25,15 +25,3 @@ target_link_libraries(test_producer
|
||||
|
||||
add_test(NAME test_producer COMMAND test_producer)
|
||||
|
||||
add_executable(test_ipc
|
||||
test_unix_ipc.cxx
|
||||
)
|
||||
|
||||
target_link_libraries(test_ipc
|
||||
PRIVATE
|
||||
core
|
||||
gtest
|
||||
gtest_main
|
||||
)
|
||||
|
||||
add_test(NAME test_ipc COMMAND test_ipc)
|
||||
|
||||
@ -1,220 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "UnixIpcBridge.hpp"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: a minimal UNIX-domain socket server that accepts one connection,
|
||||
// reads `count` ints, then tears down cleanly. Uses a ready-flag so the
|
||||
// client never races against bind/listen.
|
||||
// ---------------------------------------------------------------------------
|
||||
class FakeConsumer
|
||||
{
|
||||
public:
|
||||
explicit FakeConsumer(const std::string& path, int count = 1)
|
||||
: m_path(path), m_count(count)
|
||||
{
|
||||
// Remove stale socket from any previous failed run
|
||||
unlink(m_path.c_str());
|
||||
}
|
||||
|
||||
/// Start the server on a background thread.
|
||||
void start()
|
||||
{
|
||||
m_thread = std::thread([this] { run(); });
|
||||
|
||||
// Spin until the server signals it is listening (bounded wait).
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||
while (!m_ready.load(std::memory_order_acquire))
|
||||
{
|
||||
if (std::chrono::steady_clock::now() > deadline)
|
||||
{
|
||||
throw std::runtime_error("FakeConsumer: server failed to start");
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
/// Block until the server thread finishes.
|
||||
void join() { m_thread.join(); }
|
||||
|
||||
/// Values received in order.
|
||||
const std::vector<int>& received() const { return m_received; }
|
||||
|
||||
~FakeConsumer() { unlink(m_path.c_str()); }
|
||||
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
m_server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
ASSERT_GE(m_server_fd, 0) << "socket() failed: " << strerror(errno);
|
||||
|
||||
struct sockaddr_un addr = {};
|
||||
addr.sun_family = AF_UNIX;
|
||||
std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1);
|
||||
|
||||
ASSERT_EQ(
|
||||
bind(m_server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), 0)
|
||||
<< "bind() failed: " << strerror(errno);
|
||||
ASSERT_EQ(listen(m_server_fd, 1), 0)
|
||||
<< "listen() failed: " << strerror(errno);
|
||||
|
||||
// Signal that we are ready to accept connections.
|
||||
m_ready.store(true, std::memory_order_release);
|
||||
|
||||
int client_fd = accept(m_server_fd, nullptr, nullptr);
|
||||
ASSERT_GE(client_fd, 0) << "accept() failed: " << strerror(errno);
|
||||
|
||||
for (int i = 0; i < m_count; ++i)
|
||||
{
|
||||
int value = 0;
|
||||
ssize_t n = recv(client_fd, &value, sizeof(value), MSG_WAITALL);
|
||||
ASSERT_EQ(n, static_cast<ssize_t>(sizeof(value)))
|
||||
<< "recv() short read on message " << i;
|
||||
m_received.push_back(value);
|
||||
}
|
||||
|
||||
close(client_fd);
|
||||
close(m_server_fd);
|
||||
}
|
||||
|
||||
std::string m_path;
|
||||
int m_count;
|
||||
int m_server_fd = -1;
|
||||
std::atomic<bool> m_ready{false};
|
||||
std::thread m_thread;
|
||||
std::vector<int> m_received;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Sends a single integer value and verifies the consumer receives it.
|
||||
TEST(UnixIpcBridgeTest, SendsSingleInt)
|
||||
{
|
||||
const std::string sock = "/tmp/test_ipc_single.sock";
|
||||
|
||||
FakeConsumer consumer(sock, /*count=*/1);
|
||||
consumer.start();
|
||||
|
||||
UnixIpcBridge bridge(sock);
|
||||
bridge.send(42);
|
||||
|
||||
consumer.join();
|
||||
|
||||
ASSERT_EQ(consumer.received().size(), 1u);
|
||||
EXPECT_EQ(consumer.received()[0], 42);
|
||||
}
|
||||
|
||||
/// Sends zero and a negative value — makes sure sign bits survive.
|
||||
TEST(UnixIpcBridgeTest, SendsZeroAndNegativeValues)
|
||||
{
|
||||
// Zero
|
||||
{
|
||||
const std::string sock = "/tmp/test_ipc_zero.sock";
|
||||
FakeConsumer consumer(sock, 1);
|
||||
consumer.start();
|
||||
|
||||
UnixIpcBridge bridge(sock);
|
||||
bridge.send(0);
|
||||
|
||||
consumer.join();
|
||||
ASSERT_EQ(consumer.received().size(), 1u);
|
||||
EXPECT_EQ(consumer.received()[0], 0);
|
||||
}
|
||||
|
||||
// Negative
|
||||
{
|
||||
const std::string sock = "/tmp/test_ipc_neg.sock";
|
||||
FakeConsumer consumer(sock, 1);
|
||||
consumer.start();
|
||||
|
||||
UnixIpcBridge bridge(sock);
|
||||
bridge.send(-1);
|
||||
|
||||
consumer.join();
|
||||
ASSERT_EQ(consumer.received().size(), 1u);
|
||||
EXPECT_EQ(consumer.received()[0], -1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends INT_MAX / INT_MIN to check for truncation or overflow.
|
||||
TEST(UnixIpcBridgeTest, SendsExtremeBoundaryValues)
|
||||
{
|
||||
{
|
||||
const std::string sock = "/tmp/test_ipc_max.sock";
|
||||
FakeConsumer consumer(sock, 1);
|
||||
consumer.start();
|
||||
|
||||
UnixIpcBridge bridge(sock);
|
||||
bridge.send(std::numeric_limits<int>::max());
|
||||
|
||||
consumer.join();
|
||||
ASSERT_EQ(consumer.received().size(), 1u);
|
||||
EXPECT_EQ(consumer.received()[0], std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
{
|
||||
const std::string sock = "/tmp/test_ipc_min.sock";
|
||||
FakeConsumer consumer(sock, 1);
|
||||
consumer.start();
|
||||
|
||||
UnixIpcBridge bridge(sock);
|
||||
bridge.send(std::numeric_limits<int>::min());
|
||||
|
||||
consumer.join();
|
||||
ASSERT_EQ(consumer.received().size(), 1u);
|
||||
EXPECT_EQ(consumer.received()[0], std::numeric_limits<int>::min());
|
||||
}
|
||||
}
|
||||
|
||||
/// Connecting to a non-existent socket must throw, not silently fail.
|
||||
TEST(UnixIpcBridgeTest, ThrowsWhenNoConsumerListening)
|
||||
{
|
||||
const std::string sock = "/tmp/test_ipc_noserver.sock";
|
||||
unlink(sock.c_str()); // make sure nothing is there
|
||||
|
||||
UnixIpcBridge bridge(sock);
|
||||
EXPECT_THROW(bridge.send(99), std::runtime_error);
|
||||
}
|
||||
|
||||
/// Multiple sequential sends (each reopens the connection).
|
||||
TEST(UnixIpcBridgeTest, MultipleSendsSequentially)
|
||||
{
|
||||
const std::string sock = "/tmp/test_ipc_multi.sock";
|
||||
constexpr int kMessages = 5;
|
||||
|
||||
// Server expects exactly kMessages ints from kMessages connections.
|
||||
// Because the bridge reconnects every send(), we run kMessages
|
||||
// single-message consumers sequentially.
|
||||
std::vector<int> all_received;
|
||||
for (int i = 0; i < kMessages; ++i)
|
||||
{
|
||||
FakeConsumer consumer(sock, 1);
|
||||
consumer.start();
|
||||
|
||||
UnixIpcBridge bridge(sock);
|
||||
bridge.send(i * 10);
|
||||
|
||||
consumer.join();
|
||||
ASSERT_EQ(consumer.received().size(), 1u);
|
||||
all_received.push_back(consumer.received()[0]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(all_received.size(), static_cast<size_t>(kMessages));
|
||||
for (int i = 0; i < kMessages; ++i)
|
||||
{
|
||||
EXPECT_EQ(all_received[i], i * 10);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user