add logging and other things
This commit is contained in:
parent
a4650fd589
commit
067cadbe0f
10 changed files with 870 additions and 98 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue