// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Child processes and their lifetime. // // SIGKILL to the claude process alone would leave the Playwright server and // Chromium under it running after a timeout, and every pass running after a // SIGKILLed driver, so a pass runs under a supervisor that is the subreaper // of its tree. This test starts a target that leaves a detached grandchild — // `setsid sleep`, a process group of its own, the way Playwright starts // Chromium — and checks the grandchild is gone when the target exits, when // the supervisor is told to stop, and when the driver above it is SIGKILLed. // // Also: return codes (minus the signal for a killed child), a missing // program reported with its errno text, captured output from both streams // without a deadlock, a timeout, nothing but the three streams reaching the // child, and the child getting SIGPIPE back at its default with an empty // signal mask while the driver ignores SIGPIPE. // // The test binary plays the supervisor (`__supervise`) and the driver // (`--as-driver`) itself, by re-running its own executable. #include #include #include #include import std; import Crafter.Review; namespace fs = std::filesystem; using namespace Crafter::Review; using namespace std::chrono_literals; namespace { std::int32_t Failures = 0; void Check(bool cond, std::string_view msg) { if (!cond) { std::println(std::cerr, "FAIL: {}", msg); ++Failures; } } fs::path Stage() { const fs::path dir = fs::temp_directory_path() / std::format("crafter-review-process-{}", getpid()); fs::remove_all(dir); fs::create_directories(dir); return dir; } bool Alive(std::int64_t pid) { return pid > 0 && kill(static_cast(pid), 0) == 0; } // The pid a target wrote to `file`, waited for up to 5 s. std::int64_t PidFrom(const fs::path& file) { for (std::int32_t i = 0; i < 100; ++i) { if (const std::optional text = Platform::ReadFile(file)) { if (const std::optional pid = Dyn::ParseInt(*text); pid && *pid > 0) return *pid; } std::this_thread::sleep_for(50ms); } return 0; } bool GoneWithin(std::int64_t pid, std::chrono::milliseconds limit) { const auto deadline = std::chrono::steady_clock::now() + limit; while (std::chrono::steady_clock::now() < deadline) { if (!Alive(pid)) return true; std::this_thread::sleep_for(20ms); } return !Alive(pid); } // `sh -c '