add logging and other things

This commit is contained in:
Hedy88 2026-08-28 23:24:19 +01:00
commit 067cadbe0f
No known key found for this signature in database
10 changed files with 870 additions and 98 deletions

View file

@ -4,6 +4,7 @@
#include "../src/supervisor.cpp"
#include <cstdio>
#include <chrono>
#include <map>
#include <memory>
#include <sstream>
@ -166,6 +167,58 @@ void test_parse_basic() {
"unknown action command");
// unknown directive at column 0
CHECK_THROWS(parse_string("BROKEN = yes\n", fs), "unexpected directive");
// switch_root parses as a SwitchRoot command
auto sr = parse_string("on boot\n switch_root /mnt/root /sbin/init --stage2\n",
fs);
CHECK_EQ(sr.actions.size(), 1u);
CHECK_EQ(sr.actions[0].commands.size(), 1u);
CHECK(sr.actions[0].commands[0].kind == Command::Kind::SwitchRoot);
CHECK_EQ(sr.actions[0].commands[0].args.size(), 3u);
CHECK_EQ(sr.actions[0].commands[0].args[0], std::string("/mnt/root"));
CHECK_EQ(sr.actions[0].commands[0].args[1], std::string("/sbin/init"));
CHECK_EQ(sr.actions[0].commands[0].args[2], std::string("--stage2"));
// `depends = A B ...` is a repeatable, multi-valued service option
auto dp = parse_string(
"service web /usr/sbin/httpd\n"
" depends = net\n"
" depends = db cache\n",
fs);
CHECK_EQ(dp.services.size(), 1u);
CHECK_EQ(dp.services[0].depends.size(), 3u);
CHECK_EQ(dp.services[0].depends[0], std::string("net"));
CHECK_EQ(dp.services[0].depends[1], std::string("db"));
CHECK_EQ(dp.services[0].depends[2], std::string("cache"));
// setprop/getprop action commands and `on property:` triggers
auto pp = parse_string(
"on boot\n"
" setprop net.up 1\n"
" getprop net.up\n"
"on property:net.up=1\n"
" log network is up\n",
fs);
CHECK_EQ(pp.actions.size(), 2u);
// property trigger string is captured verbatim
CHECK_EQ(pp.actions[1].trigger, std::string("property:net.up=1"));
CHECK_EQ(pp.actions[0].commands[0].kind, Command::Kind::Setprop);
CHECK_EQ(pp.actions[0].commands[0].args.size(), 2u);
CHECK_EQ(pp.actions[0].commands[0].args[0], std::string("net.up"));
CHECK_EQ(pp.actions[0].commands[0].args[1], std::string("1"));
CHECK(pp.actions[0].commands[1].kind == Command::Kind::Getprop);
// `logfile = PATH` is an optional service option
auto lf = parse_string(
"service daemon /usr/sbin/daemon\n"
" logfile = /var/log/daemon.log\n",
fs);
CHECK_EQ(lf.services.size(), 1u);
CHECK_EQ(lf.services[0].logfile, std::string("/var/log/daemon.log"));
auto nl = parse_string(
"service plain /usr/sbin/plain\n",
fs);
CHECK_EQ(nl.services[0].logfile, std::string(""));
}
void test_imports() {
@ -262,6 +315,126 @@ void test_supervisor_helpers() {
std::string("web running pid 42 oneshot class tools\n"));
}
void test_crash_window() {
// a service that crashes repeatedly inside the window gets throttled at
// the threshold, then recovers once the window elapses.
using Clock = std::chrono::steady_clock;
using namespace std::chrono;
Service svc;
svc.name = "boom";
svc.crash_threshold = 2;
svc.crash_window_secs = 10;
auto t = Clock::now();
// first two crashes within the window are allowed
CHECK(record_crash(svc, t) == CrashAction::Respawn);
CHECK_EQ(svc.crash_count, 1);
CHECK(!svc.throttled);
CHECK(record_crash(svc, t + seconds(1)) == CrashAction::Respawn);
CHECK_EQ(svc.crash_count, 2);
CHECK(!svc.throttled);
// third crash crosses the threshold -> throttle
CHECK(record_crash(svc, t + seconds(2)) == CrashAction::Throttle);
CHECK_EQ(svc.crash_count, 3);
CHECK(svc.throttled);
// Once a fresh window opens (the crash was long after the last one), the
// count resets but `throttled` stays set until an explicit start -- the
// reaper stops respawning a throttled service, so the only recovery is a
// manual `start`/`restart` (reset_crash_state below).
CHECK(record_crash(svc, t + seconds(11)) == CrashAction::Respawn);
CHECK_EQ(svc.crash_count, 1);
CHECK(svc.throttled);
// a manual start resets the throttle, opening a fresh window.
reset_crash_state(svc);
CHECK_EQ(svc.crash_count, 0);
CHECK(!svc.throttled);
// threshold=1 means a single crash then throttle.
Service one;
one.crash_threshold = 1;
one.crash_window_secs = 10;
CHECK(record_crash(one, t) == CrashAction::Respawn);
CHECK(record_crash(one, t + seconds(1)) == CrashAction::Throttle);
// a degenerate (unset) window never throttles.
Service none;
none.crash_threshold = 0;
none.crash_window_secs = 0;
for (int i = 0; i < 100; ++i)
CHECK(record_crash(none, t + seconds(i)) == CrashAction::Respawn);
CHECK_EQ(none.crash_count, 0);
CHECK(!none.throttled);
}
void test_dependencies() {
Config cfg;
auto add = [&](const std::string& name, std::initializer_list<const char*> deps) {
Service s;
s.name = name;
for (auto* d : deps)
s.depends.emplace_back(d);
cfg.services.push_back(std::move(s));
};
add("a", {});
add("b", {"a"});
add("c", {"b"});
add("d", {"b", "c"}); // depends on a chain + a sibling
add("e", {});
// linear chain: c -> b -> a
std::vector<std::string> order;
CHECK(resolve_dependencies(cfg, "c", order) == DepResolve::Ok);
CHECK_EQ(order.size(), 3u);
CHECK(order[0] == "a");
CHECK(order[1] == "b");
CHECK(order[2] == "c");
// declared order is preserved; shared deps appear once (diamond / DAG)
order.clear();
CHECK(resolve_dependencies(cfg, "d", order) == DepResolve::Ok);
CHECK_EQ(order.size(), 4u);
CHECK(order[0] == "a"); // b's dep
CHECK(order[1] == "b"); // first declared dep of d
CHECK(order[2] == "c"); // second declared dep of d (and c->b already done)
CHECK(order[3] == "d");
// a service with no deps yields just itself
order.clear();
CHECK(resolve_dependencies(cfg, "e", order) == DepResolve::Ok);
CHECK_EQ(order.size(), 1u);
CHECK(order[0] == "e");
// unknown dependency
Service ghost;
ghost.name = "ghost";
ghost.depends.push_back("nope");
cfg.services.push_back(std::move(ghost));
order.clear();
CHECK(resolve_dependencies(cfg, "ghost", order) == DepResolve::Unknown);
// self-cycle
Service self;
self.name = "self";
self.depends.push_back("self");
cfg.services.push_back(std::move(self));
order.clear();
CHECK(resolve_dependencies(cfg, "self", order) == DepResolve::Cycle);
// mutual cycle a<->b
Service m1, m2;
m1.name = "m1";
m1.depends.push_back("m2");
m2.name = "m2";
m2.depends.push_back("m1");
cfg.services.push_back(std::move(m1));
cfg.services.push_back(std::move(m2));
order.clear();
CHECK(resolve_dependencies(cfg, "m1", order) == DepResolve::Cycle);
}
} // namespace
int main() {
@ -271,6 +444,8 @@ int main() {
test_parse_basic();
test_imports();
test_supervisor_helpers();
test_crash_window();
test_dependencies();
std::printf("%d checks, %d failures\n", g_checks, g_failures);
return g_failures == 0 ? 0 : 1;