nix/tests/rwhod: init
Build and test / build (push) Successful in 1m41s
Build and test / check (push) Successful in 2m24s
Build and test / docs (push) Successful in 3m58s
Build and test / test (push) Successful in 2m7s

This commit is contained in:
2026-07-21 16:07:38 +09:00
parent a1d83a3ad3
commit f5090f9c8c
3 changed files with 178 additions and 0 deletions
+6
View File
@@ -122,5 +122,11 @@
# NOTE: the non-crane build runs tests during checkPhase
inherit (self.packages.${system}) roowho2;
});
tests = forAllSystems (system: pkgs: _: let
pkgs' = pkgs.extend self.overlays.default;
in
import ./nix/tests { pkgs = pkgs'; }
);
};
}
+10
View File
@@ -0,0 +1,10 @@
{ pkgs, ... }:
{
# Backwards compatibility tests
# bsd-fingerd = pkgs.testers.runNixOSTest ./bsd-fingerd.nix;
# netkit-rwho = pkgs.testers.runNixOSTest ./netkit-rwho.nix;
# roowho2 integration tests
rwhod = pkgs.testers.runNixOSTest ./rwhod.nix;
# fingerd = pkgs.testers.runNixOSTest ./fingerd.nix;
}
+162
View File
@@ -0,0 +1,162 @@
{ ... }:
{
name = "rwhod";
nodes = {
host1 = { pkgs, ... }: {
imports = [ ../module.nix ];
boot.kernelParams = [ "audit=1" ];
environment.systemPackages = [ pkgs.screen ];
services.roowho2 = {
enable = true;
settings = {
log_level = "debug";
rwhod = {
enable = true;
# Effectively infinite interval means host1 will never announce itself
# to the network in a periodic broadcast. We will force it to do so
# via the realtime push mechanism when a user logs in or out.
send_interval_seconds = 99999999;
};
fingerd.enable = false;
};
};
users.users.alice.isNormalUser = true;
};
host2 = { pkgs, ... }: {
imports = [ ../module.nix ];
environment.systemPackages = [ pkgs.screen ];
services.roowho2 = {
enable = true;
settings = {
log_level = "debug";
rwhod = {
enable = true;
send_interval_seconds = 5;
};
fingerd.enable = false;
};
};
users.users.bob.isNormalUser = true;
specialisation.ignore-bob.configuration = {
services.roowho2.settings.rwhod.ignoreUsers = [ "bob" ];
};
};
host3 = { ... }: {
imports = [ ../module.nix ];
services.roowho2 = {
enable = true;
settings = {
log_level = "debug";
rwhod = {
enable = true;
send_interval_seconds = 5;
# Without any interfaces to broadcast on,
# this host will never announce itself to the network.
interfaces = [ ];
};
fingerd.enable = false;
};
};
};
};
testScript = ''
import json
def rwho_entries(machine):
return json.loads(machine.succeed("rwho --all --json"))
def ruptime_entries(machine):
return json.loads(machine.succeed("ruptime --all --json"))
def find_ruptime_entry(entries, hostname):
return next((e for e in entries if e["hostname"] == hostname), None)
def find_rwho_user(entries, hostname, username):
return next(
(user for user in entries.get(hostname, []) if user["user_id"] == username),
None,
)
def wait_for_host_in_ruptime(machine, hostname, present=True, timeout=300):
def check(_last_try):
found = find_ruptime_entry(ruptime_entries(machine), hostname) is not None
return found == present
retry(check, timeout_seconds=timeout)
def wait_for_user_in_rwho(machine, hostname, username, present=True, timeout=300):
def check(_last_try):
found = find_rwho_user(rwho_entries(machine), hostname, username) is not None
return found == present
retry(check, timeout_seconds=timeout)
def start_session(machine, username, session_name):
machine.succeed(f"screen -dmS {session_name} login -f {username}")
def stop_session(machine, session_name):
machine.succeed(f"screen -S {session_name} -X quit")
start_all()
for machine in [host1, host2, host3]:
machine.wait_for_unit("roowho2-rwhod.socket")
machine.wait_for_unit("roowho2-client.socket")
machine.wait_for_unit("multi-user.target")
machine.succeed("ruptime --all --json")
with subtest("host2 broadcasts its status periodically"):
host2.wait_for_console_text(r"Sending rwhod packet to interface")
wait_for_host_in_ruptime(host1, "host2", timeout=20)
wait_for_host_in_ruptime(host3, "host2", timeout=20)
with subtest("Check host2 ruptime fields"):
entry = find_ruptime_entry(ruptime_entries(host1), "host2")
assert entry is not None, "Expected host2 to be present in host1's ruptime"
assert entry["users"] == [], f"Expected no users logged in yet, got: {entry!r}"
with subtest("Starting a session on host1 triggers a realtime status push"):
start_session(host1, "alice", "alice-session")
wait_for_user_in_rwho(host2, "host1", "alice")
wait_for_user_in_rwho(host3, "host1", "alice")
with subtest("Stopping a session on host1 triggers a realtime status push"):
stop_session(host1, "alice-session")
wait_for_user_in_rwho(host2, "host1", "alice", present=False)
wait_for_user_in_rwho(host3, "host1", "alice", present=False)
with subtest("Starting a session on host2 eventually becomes visible"):
start_session(host2, "bob", "bob-session")
wait_for_user_in_rwho(host1, "host2", "bob")
wait_for_user_in_rwho(host3, "host2", "bob")
with subtest("Switching host2 to ignore bob removes them from the next broadcast"):
host2.succeed(
"/run/current-system/specialisation/ignore-bob/bin/switch-to-configuration test"
)
host2.wait_for_unit("roowho2-client.socket")
host2.succeed("who | grep bob") # Ensure bob is still logged in
wait_for_user_in_rwho(host1, "host2", "bob", present=False)
wait_for_user_in_rwho(host3, "host2", "bob", present=False)
with subtest("host3's interfaces allowlist keeps it from ever broadcasting"):
assert find_ruptime_entry(ruptime_entries(host1), "host3") is None, (
"host1 should not have learned about host3"
)
assert find_ruptime_entry(ruptime_entries(host2), "host3") is None, (
"host2 should not have learned about host3"
)
wait_for_host_in_ruptime(host3, "host1")
wait_for_host_in_ruptime(host3, "host2")
'';
}