2 Commits

2 changed files with 30 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
# Quality Description
Writing tests first forced every component to be injectable and independently exercisable before any integration happened. That constraint turned out to matter more than expected when the race-condition tests were added at the end: because the producer, sysfs reader, and IPC bridge had already been broken into units with explicit interfaces (`std::function` callbacks, injected sleep, injected logger), the stress tests could be wired up without touching any production code. Nothing needed to be refactored to be testable — it already was. That is the practical benefit of TDD for concurrent embedded software: the discipline of writing the test first tends to eliminate shared mutable state and deep call chains by making them painful to test, which in turn reduces cyclomatic complexity almost as a side effect.

View File

@@ -3,6 +3,9 @@
// Author: Unai Blazquez <unaibg2000@gmail.com>
#include <gtest/gtest.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <QCoreApplication>
#include <QSignalSpy>
@@ -137,8 +140,28 @@ TEST(RaceConditionTest, ProducerSurvivesConsumerCrash)
}
ASSERT_GE(spy.count(), 2) << "Phase 1: producer should have delivered values";
// "Crash" the consumer: stop + destroy.
consumer.stop();
// Simulate a hard crash: force-close the consumer's server fd from
// outside its thread, causing accept() to fail with EBADF. This is
// what happens when the kernel reclaims fds on SIGKILL / abort().
//
// We find the server fd by calling getsockname() on open fds and
// matching against our socket path.
for (int fd = 3; fd < 1024; ++fd)
{
struct sockaddr_un addr = {};
socklen_t len = sizeof(addr);
if (getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) == 0 &&
addr.sun_family == AF_UNIX &&
std::string(addr.sun_path) == sock)
{
::close(fd); // Yank the fd — consumer thread crashes out of accept()
break;
}
}
// Destructor calls stop(), which joins the (now-exited) thread and
// cleans up. In a real crash no cleanup runs, but we can't leak
// threads in a test process.
}
// Phase 2: producer is still running with no consumer (sends will fail).