mind-wake process tests: HUGINN_ENDPOINT is the fallback, and in a container the real bin and daemon record this machine's host
The env test pins the variable's name and that the flag wins over it. The ignored container test seeds a mind, serves it with the real huginn-daemon, writes the brake at its real root and runs the real mind-wake with an empty PATH (so no unit is reached): the run it opens must carry /proc/sys/kernel/hostname as host and mind-wake as agent, which kills the three host() mutants of finding host-survivors-reachable. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
61766cab17
commit
6f4f970c1a
1 changed files with 151 additions and 2 deletions
|
|
@ -1,9 +1,25 @@
|
|||
//! The `mind-wake` binary as a process: its arguments and its exit codes. The
|
||||
//! brake's root is a constant, so a process test can only ever see the absent
|
||||
//! store of an instance no host has; the cycle's behaviour is tested in the
|
||||
//! library against a real mind and a real store.
|
||||
//! library against a real mind and a real store. One test, `#[ignore]`d outside a
|
||||
//! container, puts the real binary, the real daemon and a real store at the
|
||||
//! brake's root together, which is where the host a run records can be seen.
|
||||
|
||||
use std::process::{Command, Output};
|
||||
use std::net::UdpSocket;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, Output, Stdio};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Utc;
|
||||
use cultnet_rs::Selection;
|
||||
use eureka_pipeline::{
|
||||
Date, DocRef, PipelineCampaign, PipelineDocument, PipelineInstance, PipelineKind, PipelineStewardship, PipelineTarget, Sha, Slug, TargetInvariant,
|
||||
};
|
||||
use huginn_mind::{Faculty, Mind, PipelineAdmissionBatch, PipelineAdmissionOutcome, PipelinePageItems, PipelineProvenance};
|
||||
use mind_body::control::{BurnRate, CONTROL_ROOT, ControlWriter, control_path};
|
||||
use rust_decimal::Decimal;
|
||||
use tempfile::TempDir;
|
||||
|
||||
const NO_SUCH_INSTANCE: &str = "wake-test-no-such-instance";
|
||||
|
||||
|
|
@ -11,6 +27,11 @@ fn wake(args: &[&str]) -> Output {
|
|||
Command::new(env!("CARGO_BIN_EXE_mind-wake")).args(args).env_remove("HUGINN_ENDPOINT").output().unwrap()
|
||||
}
|
||||
|
||||
/// The binary with `HUGINN_ENDPOINT` set to `endpoint`.
|
||||
fn wake_with_env_endpoint(args: &[&str], endpoint: &str) -> Output {
|
||||
Command::new(env!("CARGO_BIN_EXE_mind-wake")).args(args).env("HUGINN_ENDPOINT", endpoint).output().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_an_instance_or_with_a_stray_argument_it_exits_2_with_the_usage_and_echoes_nothing() {
|
||||
let canary = "canary-argument-3f9a";
|
||||
|
|
@ -49,3 +70,131 @@ fn a_malformed_endpoint_or_none_is_exit_2_and_the_endpoint_is_not_echoed() {
|
|||
let none = wake(&["--instance", NO_SUCH_INSTANCE]);
|
||||
assert_eq!(none.status.code(), Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_the_flag_the_daemon_is_found_through_huginn_endpoint_and_a_bad_value_is_not_echoed() {
|
||||
// With no endpoint at all the cycle exits 2 (above); with the variable set it
|
||||
// goes on to the brake, which holds an instance no host has, and exits 0.
|
||||
let held = wake_with_env_endpoint(&["--instance", NO_SUCH_INSTANCE], "rudp://127.0.0.1:9");
|
||||
assert_eq!(held.status.code(), Some(0), "{}", String::from_utf8_lossy(&held.stderr));
|
||||
assert_eq!(String::from_utf8_lossy(&held.stdout).trim(), "held: BrakeAbsent");
|
||||
|
||||
let canary = "canary-env-endpoint-4d02";
|
||||
let bad = wake_with_env_endpoint(&["--instance", NO_SUCH_INSTANCE], canary);
|
||||
assert_eq!(bad.status.code(), Some(2));
|
||||
assert!(!String::from_utf8_lossy(&bad.stderr).contains(canary) && bad.stdout.is_empty());
|
||||
|
||||
// The flag wins over the variable: a valid flag with a bad variable still holds.
|
||||
let flag = wake_with_env_endpoint(&["--instance", NO_SUCH_INSTANCE, "--endpoint", "rudp://127.0.0.1:9"], canary);
|
||||
assert_eq!(flag.status.code(), Some(0));
|
||||
}
|
||||
|
||||
const INSTANCE: &str = "yggdrasil";
|
||||
const CAMPAIGN: &str = "eureka-body";
|
||||
const REPO: &str = "GameCult/Huginn";
|
||||
|
||||
fn doc_ref() -> DocRef {
|
||||
DocRef { path: "docs/eureka-body-target.md".into(), start_line: 1, end_line: 9, commit: Sha("df7507b".into()) }
|
||||
}
|
||||
|
||||
/// The identity, stewardship, campaign and target a Persona turn's run cites.
|
||||
fn seed() -> Vec<PipelineDocument> {
|
||||
let date = || Date("2026-10-09".into());
|
||||
vec![
|
||||
PipelineDocument::Instance(PipelineInstance { instance: Slug(INSTANCE.into()), display_name: "test mind".into(), created_at: date(), host: "test".into() }),
|
||||
PipelineDocument::Stewardship(PipelineStewardship { instance: Slug(INSTANCE.into()), repo: REPO.into(), sequence: 1, assigned_on: date(), note: "assigned".into() }),
|
||||
PipelineDocument::Campaign(PipelineCampaign {
|
||||
slug: Slug(CAMPAIGN.into()),
|
||||
title: "Eureka body".into(),
|
||||
repos: vec![REPO.into()],
|
||||
working_branch: "main".into(),
|
||||
target_doc: doc_ref(),
|
||||
}),
|
||||
PipelineDocument::Target(PipelineTarget {
|
||||
campaign: Slug(CAMPAIGN.into()),
|
||||
revision: 1,
|
||||
invariants: vec![TargetInvariant { label: "run-is-the-grant".into(), statement: "A run is the grant.".into() }],
|
||||
not_in_scope: vec![],
|
||||
canonical_implementations: vec![],
|
||||
doc: doc_ref(),
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
/// The daemon, ended with the test whether it passes or not.
|
||||
struct Daemon(Child);
|
||||
|
||||
impl Drop for Daemon {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.0.kill();
|
||||
let _ = self.0.wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// The real binary against the real daemon and a real store at the brake's
|
||||
/// root. It writes `/etc/gamecult/minds`, so it runs in a container only
|
||||
/// (`/.dockerenv`), as root, after `cargo build -p huginn-daemon` has put the
|
||||
/// daemon beside the binary. `PATH` is empty so no unit is ever reached: the
|
||||
/// run opens, its unit fails to start and the run is withdrawn, which is the
|
||||
/// outcome printed.
|
||||
#[test]
|
||||
#[ignore = "container only: root, /etc/gamecult/minds and a built huginn-daemon beside the binary"]
|
||||
fn the_real_bin_against_the_real_daemon_opens_a_run_that_records_this_machines_host_and_the_agent() {
|
||||
assert!(Path::new("/.dockerenv").exists(), "this test writes the brake's root and runs in a container only");
|
||||
let slug = Slug(INSTANCE.into());
|
||||
let state = TempDir::new().unwrap();
|
||||
{
|
||||
let mut mind = Mind::open(state.path(), &slug).unwrap();
|
||||
let batch = PipelineAdmissionBatch {
|
||||
instance: slug.clone(),
|
||||
provenance: PipelineProvenance { faculty: Faculty::Hands, agent: "test".into(), session: "test".into(), tool: "test".into() },
|
||||
documents: seed(),
|
||||
};
|
||||
assert!(matches!(mind.admit(batch, Utc::now()), PipelineAdmissionOutcome::Committed { .. }));
|
||||
}
|
||||
let mut writer = ControlWriter::open(control_path(Path::new(CONTROL_ROOT), &slug).unwrap()).unwrap();
|
||||
writer.set_brake(true, Utc::now(), "op").unwrap();
|
||||
let dial = BurnRate { heat: Decimal::from_str("1").unwrap(), base_cooldown_s: 600, base_run_usd: Decimal::from_str("5").unwrap(), set_at: Utc::now(), set_by: "op".into() };
|
||||
writer.set_dial(dial).unwrap();
|
||||
|
||||
let port = UdpSocket::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port();
|
||||
let endpoint = format!("rudp://127.0.0.1:{port}");
|
||||
let wake_exe = Path::new(env!("CARGO_BIN_EXE_mind-wake"));
|
||||
let daemon_exe = wake_exe.with_file_name("huginn-daemon");
|
||||
assert!(daemon_exe.exists(), "build huginn-daemon first: {}", daemon_exe.display());
|
||||
let daemon = Daemon(
|
||||
Command::new(&daemon_exe)
|
||||
.args(["--state-root", state.path().to_str().unwrap(), "--instance", INSTANCE, "--bind", &format!("127.0.0.1:{port}")])
|
||||
.args(["--qdrant-url", "http://127.0.0.1:1", "--ollama-url", "http://127.0.0.1:1", "--embedding-model", "x"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// Exit 2 before the daemon answers has opened nothing; try again until it does.
|
||||
let mut output = None;
|
||||
for _ in 0..40 {
|
||||
let attempt = Command::new(wake_exe).args(["--instance", INSTANCE, "--endpoint", &endpoint]).env("PATH", "").output().unwrap();
|
||||
if attempt.status.code() != Some(2) {
|
||||
output = Some(attempt);
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
let output = output.expect("the daemon never answered");
|
||||
assert_eq!(output.status.code(), Some(0), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "refused: start_failed");
|
||||
drop(daemon);
|
||||
|
||||
let hostname = std::fs::read_to_string("/proc/sys/kernel/hostname").unwrap().trim().to_string();
|
||||
assert!(!hostname.is_empty());
|
||||
let mind = Mind::open(state.path(), &slug).unwrap();
|
||||
let runs = Selection { schemas: Some(vec![PipelineKind::Run.type_id().to_string()]), ..Selection::default() };
|
||||
let PipelinePageItems::Headers(headers) = mind.query(&runs).unwrap().items else { panic!("documents to a header selection") };
|
||||
assert_eq!(headers.len(), 1);
|
||||
let view = mind.view(&headers[0].id).unwrap().unwrap();
|
||||
let PipelineDocument::Run(run) = &view.document else { panic!("not a run") };
|
||||
assert_eq!(run.host.0, hostname);
|
||||
assert_eq!(view.admission.provenance.agent.0, "mind-wake");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue