tests/install_matrix_live.rs
//! LIVE multi-OS install matrix (heavy — boots real KVM guests). `#[ignore]` so the
//! default `cargo test` stays hermetic; run it explicitly on a KVM-capable host:
//!
//! ```bash
//! # build the injected static probe + test-service first:
//! cargo build --release --target x86_64-unknown-linux-musl --manifest-path probe/Cargo.toml
//! # then the matrix (optionally SKID_ONLY=ubuntu-24.04,alpine-3.21 to subset):
//! cargo test --features matrix --test install_matrix_live -- --ignored --nocapture
//! ```
//!
//! Images are resolved from `$SKID_ISOS` (default `/mnt/work4t/skidbladnir/isos`);
//! per-VM scratch + the COW snapshot live under `$SKID_VMS`
//! (default `/mnt/work4t/skidbladnir/vms`); rows are sunk to `$WORKSPACE_SKIDBLADNIR`
//! (default `/mnt/work4t/skidbladnir/workspace_skidbladnir`).
#![cfg(feature = "matrix")]
use std::path::{Path, PathBuf};
use std::time::Duration;
use skidbladnir::events::JsonlSink;
use skidbladnir::matrix::{OsImage, run_matrix, sink_results};
use skidbladnir::service::Backend;
fn env_path(key: &str, default: &str) -> PathBuf {
std::env::var(key)
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(default))
}
#[test]
#[ignore = "heavy: boots real KVM guests; needs qemu + the musl probe built"]
fn live_install_matrix() {
let isos = env_path("SKID_ISOS", "/mnt/work4t/skidbladnir/isos");
let vms = env_path("SKID_VMS", "/mnt/work4t/skidbladnir/vms");
let workspace = env_path(
"WORKSPACE_SKIDBLADNIR",
"/mnt/work4t/skidbladnir/workspace_skidbladnir",
);
// The injected static binaries (built for x86_64-unknown-linux-musl).
let rel = "probe/target/x86_64-unknown-linux-musl/release";
let probe_bin = Path::new(env!("CARGO_MANIFEST_DIR"))
.join(rel)
.join("skidbladnir-probe");
let testsvc_bin = Path::new(env!("CARGO_MANIFEST_DIR"))
.join(rel)
.join("skidbladnir-testsvc");
if !probe_bin.exists() || !testsvc_bin.exists() {
panic!(
"build the probe first:\n cargo build --release --target x86_64-unknown-linux-musl \
--manifest-path probe/Cargo.toml\n(missing {} / {})",
probe_bin.display(),
testsvc_bin.display()
);
}
// The matrix axis: each OS exercises one renderer (Alpine → OpenRC; rest → systemd).
let all = vec![
OsImage::new(
"ubuntu-24.04",
isos.join("ubuntu-24.04-cloudimg-amd64.img"),
Backend::SystemdSystem,
)
.memory_mb(2048),
OsImage::new(
"debian-12",
isos.join("debian-12-genericcloud-amd64.qcow2"),
Backend::SystemdSystem,
)
.memory_mb(2048),
OsImage::new(
"fedora-42",
isos.join("fedora-42-cloud-amd64.qcow2"),
Backend::SystemdSystem,
)
.memory_mb(2048),
OsImage::new(
"alpine-3.21",
isos.join("alpine-3.21.0-x86_64-cloudinit.qcow2"),
Backend::OpenRc,
)
.memory_mb(1024),
];
let only: Option<Vec<String>> = std::env::var("SKID_ONLY")
.ok()
.map(|s| s.split(',').map(|x| x.trim().to_string()).collect());
let images: Vec<OsImage> = all
.into_iter()
.filter(|i| only.as_ref().map(|o| o.contains(&i.name)).unwrap_or(true))
.filter(|i| i.disk.exists())
.collect();
assert!(
!images.is_empty(),
"no base images found under {}",
isos.display()
);
let run_id = format!("matrix-{}", chrono::Utc::now().format("%Y%m%dT%H%M%SZ"));
let work_root = vms.join(&run_id);
std::fs::create_dir_all(&work_root).unwrap();
eprintln!("== install-matrix {run_id}: {} cell(s) ==", images.len());
let timeout = Duration::from_secs(
std::env::var("SKID_BOOT_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(360),
);
let rows = run_matrix(&images, &probe_bin, &testsvc_bin, &work_root, timeout);
// Report table.
eprintln!("\n== RESULTS (renderer × OS) ==");
for r in &rows {
eprintln!(
" [{:>9}] {:<14} {:<15} scope={}",
r.outcome.label(),
r.os,
r.backend,
r.scope
);
if let Some(rep) = r.outcome.report() {
eprintln!(
" running={} pid={:?} ports={:?} health={}/{} at={}",
rep.running, rep.pid, rep.ports, rep.health.kind, rep.health.ok, rep.asserted_at
);
}
}
// Sink rows → workspace JSONL + airgap_events (nornir autonom ingest).
let reports_jsonl = workspace.join("probe_reports.jsonl");
let sink = JsonlSink::under_warehouse(&workspace).unwrap();
sink_results(&rows, &reports_jsonl, &sink, &run_id).unwrap();
eprintln!("\nrows → {}", reports_jsonl.display());
eprintln!("events → {}", sink.path().display());
// A sample ProbeReport (the headline artifact).
if let Some(rep) = rows.iter().find_map(|r| r.outcome.report()) {
eprintln!(
"\n== sample ProbeReport ==\n{}",
serde_json::to_string_pretty(rep).unwrap()
);
}
// Assert the deliverable: at least two DISTINCT OS guests round-tripped a probe.
let with_report = rows.iter().filter(|r| r.outcome.report().is_some()).count();
assert!(
with_report >= 2,
"expected ≥2 OS guests to round-trip a probe, got {with_report}"
);
}