test(daemon): add F1/F2 process-lifecycle integration tests; fix clobbered start error
Links the real EmbeddedDaemon into the ObsidianDragonTests target (its deps were already present) and adds two POSIX integration tests that exercise the actual fork/exec/waitpid fixes headlessly: - testExecFailureReported (F2): start() against a non-executable file must fail with a precise "not executable or wrong architecture" reason. - testDaemonCrashDetected (F1): a short-lived child that exits abnormally is still detected (crash_count_ increments) while isRunning() is hammered from the test thread — a regression test for the reap race. Writing the F2 test surfaced a real bug: start()'s failure branch called setState(State::Error, "Failed to start dragonxd process"), and setState stores the Error message into last_error_ — clobbering the precise message startProcess() had just set, so getLastError()/the UI only ever saw the generic string. Fixed to pass the preserved detail to setState, so the precise reason survives and now also reaches the state callback (crash panel / status). ctest 1/1, green including the two new integration tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2634,6 +2634,59 @@ void testPlatformEnsureDirectory()
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// Integration tests that drive the REAL EmbeddedDaemon fork/exec/waitpid paths (POSIX only).
|
||||
void testExecFailureReported()
|
||||
{
|
||||
using dragonx::daemon::EmbeddedDaemon;
|
||||
namespace fsn = std::filesystem;
|
||||
|
||||
// A present-but-non-executable file: execv() must fail, and the F2 self-pipe handshake
|
||||
// must report it as a start FAILURE with a precise reason — not a transient "Running".
|
||||
fsn::path bin = fsn::temp_directory_path() / "od_fake_daemon_bin";
|
||||
{ std::ofstream(bin.string(), std::ios::binary) << "this is not an executable"; }
|
||||
fsn::permissions(bin, fsn::perms::owner_read, fsn::perm_options::replace); // 0400, no +x
|
||||
|
||||
EmbeddedDaemon d;
|
||||
d.setSkipPortCheck(true); // bypass the port + datadir-lock gates so we reach startProcess()
|
||||
EXPECT_TRUE(!d.start(bin.string()));
|
||||
EXPECT_TRUE(d.getLastError().find("not executable or wrong architecture") != std::string::npos);
|
||||
EXPECT_TRUE(!d.isRunning());
|
||||
|
||||
std::error_code ec; fsn::remove(bin, ec);
|
||||
}
|
||||
|
||||
void testDaemonCrashDetected()
|
||||
{
|
||||
using dragonx::daemon::EmbeddedDaemon;
|
||||
namespace fsn = std::filesystem;
|
||||
|
||||
// A tiny script that ignores the injected daemon args, lives briefly, then exits abnormally
|
||||
// — standing in for a daemon that crashes. is_script detection runs it via /bin/bash.
|
||||
fsn::path script = fsn::temp_directory_path() / "od_fake_daemon.sh";
|
||||
{ std::ofstream(script.string()) << "#!/bin/bash\nsleep 0.2\nexit 7\n"; }
|
||||
fsn::permissions(script, fsn::perms::owner_all, fsn::perm_options::replace); // +x
|
||||
|
||||
EmbeddedDaemon d;
|
||||
d.setSkipPortCheck(true);
|
||||
EXPECT_TRUE(d.start(script.string()));
|
||||
EXPECT_TRUE(d.isRunning()); // reads the atomic state_, not a racy waitpid()
|
||||
|
||||
// Hammer isRunning() the way the UI thread does while the child exits and monitorProcess()
|
||||
// reaps it. Pre-fix (F1), isRunning()'s own waitpid() could steal the reap and hide the
|
||||
// crash; with the fix the monitor is the sole reaper and always sees it.
|
||||
for (int i = 0; i < 400 && d.getCrashCount() == 0; ++i) {
|
||||
(void)d.isRunning();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
EXPECT_TRUE(d.getCrashCount() >= 1); // the unexpected exit was detected and counted
|
||||
EXPECT_TRUE(!d.isRunning()); // state_ flipped to Error
|
||||
|
||||
d.stop(); // join the monitor thread cleanly
|
||||
std::error_code ec; fsn::remove(script, ec);
|
||||
}
|
||||
#endif // !_WIN32
|
||||
|
||||
void testDatadirLockGate()
|
||||
{
|
||||
using dragonx::daemon::EmbeddedDaemon;
|
||||
@@ -6799,6 +6852,10 @@ int main()
|
||||
testWalletSecurityWorkflowExecutor();
|
||||
testDaemonShutdownPolicy();
|
||||
testDatadirLockGate();
|
||||
#ifndef _WIN32
|
||||
testExecFailureReported();
|
||||
testDaemonCrashDetected();
|
||||
#endif
|
||||
testPlatformEnsureDirectory();
|
||||
testVerifySaplingParams();
|
||||
testConnectHasStalled();
|
||||
|
||||
Reference in New Issue
Block a user