Reviewed-on: #13
azkoyen_technical_test
Azkoyen technical test implementation. Implemented (mostly) on standard C++17, but with Qt wherever it was strictly necessary.
Development approach
A Test-Driven Development (TDD) workflow was followed throughout the project. Every component — from the lowest-level file reader to the GUI window — has a corresponding Google Test suite written before or alongside the production code. This keeps each module verifiable in isolation and makes regressions immediately visible.
SysfsRead class
SysfsReader (include/SysfsRead.hpp, src/core/SysfsRead.cxx) is the lowest-level component. It opens a sysfs-like file and translates its raw text content into a SysfsStatus enum:
| File content | Status |
|---|---|
"1" |
Enabled |
"error: temp too high" |
ErrorTempTooHigh |
| empty / whitespace-only | Empty |
| file missing | Unreachable |
| anything else | UnexpectedValue |
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 — covers all five status branches by writing controlled content to a temporary file.
Producer class / thread
Producer (include/Producer.hpp, 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). All dependencies (send function, random generator, logger, sleep) are injected, so the producer has no Qt dependency and no knowledge of sockets.
Tests: tests/test_producer.cxx — verifies that the send callback is called when Enabled, and is not called for Unreachable, Empty, ErrorTempTooHigh, and UnexpectedValue. Uses an injected no-op sleep to run at full speed.
UnixIpcBridge
Note
Why UNIX domain sockets? More experience with them under Linux than with POSIX shared memory and semaphores, and they map cleanly to mockable abstractions for unit testing.
UnixIpcBridge (include/UnixIpcBridge.hpp, src/core/UnixIpcBridge.cxx) connects to a UNIX domain socket and sends a single int per call. It opens a new connection for each value, keeping the protocol stateless and simple.
Tests: tests/test_unix_ipc.cxx — spins up a FakeConsumer server, sends values through the bridge, and asserts they arrive correctly. Covers single value, zero, negative, INT_MAX/INT_MIN, multiple sequential sends, and throws-when-no-server.
ConsumerThread
ConsumerThread (include/Consumer.hpp, src/core/Consumer.cxx) is a QObject that listens on a UNIX domain socket in a background std::thread. On each received integer it:
- Prints the value to
stdout. - Emits the
valueReceived(int)Qt signal.
The server socket is created and bound inside start() before the thread is spawned, so the socket is guaranteed ready by the time start() returns — no race with the producer. Graceful shutdown is handled by stop(), which closes the file descriptor to unblock the blocking accept() call.
Tests: tests/test_consumer.cxx — uses QSignalSpy to verify single-value, multi-value, negative, and zero reception; clean stop without deadlock; stop when never started; and three corrupted-data cases (short message, empty connection, corrupted then valid).
MainWindow
MainWindow (include/MainWindow.hpp, src/app/MainWindow.cxx) is a minimal QWidget that displays the last integer received from ConsumerThread. It has no logic beyond updating a label via a slot connected to valueReceived(int) through Qt's queued connection — the GUI never blocks.
Tests: tests/test_main_window.cxx — verifies label updates on single and repeated values, and that the window title is set.
Race conditions and crash resilience
Tests: tests/test_race_conditions.cxx
RepeatedStartStopWhileProducerSends— starts and stopsConsumerThread20 times while a producer thread continuously attempts sends. A watchdog thread aborts the process if anystop()call deadlocks within 15 seconds.ProducerSurvivesConsumerCrash— simulates a hard consumer crash by force-closing the server fd from outside its thread (equivalent to the kernel reclaiming fds on SIGKILL). Verifies that the producer keeps running and successfully delivers values to a fresh consumer started afterwards.
Project structure
include/ Public headers for all core components
src/
app/ main.cxx and MainWindow.cxx — Qt application entry point
core/ Platform-independent logic: Producer, Consumer, SysfsReader, UnixIpcBridge
tests/ Google Test suites, one file per module + race conditions
docs/ Supporting documentation (see below)
build/ CMake out-of-source build directory
fake_sysfs_input Simulated sysfs control file used at runtime and in tests
Docs
| File | Contents |
|---|---|
| docs/self-assessment.md | Honest breakdown of difficulties encountered, the IPC mechanism trade-off, and the main design decision that changed mid-development |
| docs/quality_description.md | One-paragraph explanation of how TDD keeps concurrent embedded software robust and reduces cyclomatic complexity by design |
| docs/logic-flow-chart.png | Architecture diagram covering thread layout, IPC flow, error-handling paths, and GUI data flow |