Write new journal entries under logs/core and merge read-only legacy history without moving SQLite/WAL files. Atomically spool failed batches, retaining unspoolable entries for event-driven retries. Journal full trap diagnostics before UTF-8-safe status display. Port session overflow archives and followable line/byte excerpts to the still-shipped embedded agent. Document retention/recovery and cover the contracts with regressions. Include one rustfmt-only normalization in the existing SDK WASI test. Card: SAU-143
166 lines
5.0 KiB
Rust
166 lines
5.0 KiB
Rust
//! SDK plugin and tests for the native build pipeline.
|
|
struct Plugin;
|
|
#[sausage_plugin::rpc]
|
|
impl Plugin {
|
|
/// Return a greeting.
|
|
fn hello(&self) -> sausage_plugin::Result<String> {
|
|
Ok("hello".into())
|
|
}
|
|
}
|
|
fn main() {
|
|
sausage_plugin::run(Plugin);
|
|
}
|
|
#[cfg(test)]
|
|
mod tests {
|
|
#[test]
|
|
fn arithmetic() {
|
|
assert_eq!(2 + 2, 4);
|
|
}
|
|
#[test]
|
|
fn fixture_at_manifest_path() {
|
|
assert_eq!(
|
|
std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/fixture.txt")).unwrap(),
|
|
"fixture\n"
|
|
);
|
|
}
|
|
#[test]
|
|
fn current_directory() {
|
|
println!(
|
|
"WASI_CURRENT_DIR={}",
|
|
std::env::current_dir().unwrap().display()
|
|
);
|
|
}
|
|
#[sausage_plugin::test]
|
|
async fn sdk_lock() {
|
|
let guard = sausage_plugin::lock("/state/lock").await.unwrap();
|
|
assert!(guard.held());
|
|
let locks: sausage_plugin::serde_json::Value = sausage_plugin::serde_json::from_str(
|
|
&std::fs::read_to_string("/proc/core/locks.json").unwrap(),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(locks.as_object().unwrap().len(), 1);
|
|
assert!(locks.get("/state/lock").is_some());
|
|
let mut guard = Some(guard);
|
|
let mut waiting = Box::pin(std::future::IntoFuture::into_future(sausage_plugin::lock(
|
|
"/state/lock",
|
|
)));
|
|
let acquired = std::future::poll_fn(|context| {
|
|
let result = std::future::Future::poll(waiting.as_mut(), context);
|
|
if result.is_pending() {
|
|
drop(guard.take());
|
|
}
|
|
result
|
|
})
|
|
.await
|
|
.unwrap();
|
|
assert!(acquired.held());
|
|
let logs = sausage_plugin::call("core.log", sausage_plugin::serde_json::json!({}))
|
|
.await
|
|
.unwrap();
|
|
assert!(logs.is_array());
|
|
sausage_plugin::call(
|
|
"core.notify",
|
|
sausage_plugin::serde_json::json!({"name":"progress","params":{}}),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
#[sausage_plugin::test]
|
|
async fn other_plugin_fails() {
|
|
let error = sausage_plugin::call("missing.method", sausage_plugin::serde_json::json!({}))
|
|
.await
|
|
.unwrap_err();
|
|
assert!(error.to_string().contains("a test has no other plugins"));
|
|
}
|
|
#[sausage_plugin::test]
|
|
async fn unlock_order() {
|
|
for iteration in 0..300 {
|
|
let guard = sausage_plugin::lock("/state/order").await.unwrap();
|
|
assert!(guard.held());
|
|
drop(guard);
|
|
let guard = sausage_plugin::lock("/state/order")
|
|
.try_now()
|
|
.await
|
|
.unwrap();
|
|
assert!(guard.held(), "iteration {iteration}");
|
|
}
|
|
}
|
|
#[test]
|
|
fn empty_world() {
|
|
for path in [
|
|
"/plugins",
|
|
"/config",
|
|
"/state",
|
|
"/state/core/journal.sqlite",
|
|
"/logs/core/journal.sqlite",
|
|
] {
|
|
assert!(!std::path::Path::new(path).exists(), "{path}");
|
|
}
|
|
let names: Vec<_> = std::fs::read_dir("/")
|
|
.unwrap()
|
|
.map(|entry| entry.unwrap().file_name())
|
|
.collect();
|
|
// The runner mounts the package folder at its host path, so the first
|
|
// folder of that path also appears in `/` (for example `tmp` or `build`).
|
|
let package = env!("CARGO_MANIFEST_DIR")
|
|
.trim_start_matches('/')
|
|
.split('/')
|
|
.next()
|
|
.unwrap();
|
|
assert!(
|
|
names.iter().all(|name| {
|
|
["proc", "self", "run", package]
|
|
.iter()
|
|
.any(|allowed| name == allowed)
|
|
}),
|
|
"{names:?}"
|
|
);
|
|
for path in ["/proc", "/self", "/run"] {
|
|
assert!(std::path::Path::new(path).is_dir());
|
|
}
|
|
assert!(std::fs::write("/proc/forbidden", "no").is_err());
|
|
}
|
|
fn isolated() {
|
|
assert!(!std::path::Path::new("/self/own").exists());
|
|
assert!(!std::path::Path::new("/run/own").exists());
|
|
std::fs::write("/self/own", "self").unwrap();
|
|
std::fs::write("/run/own", "run").unwrap();
|
|
assert_eq!(std::fs::read_to_string("/self/own").unwrap(), "self");
|
|
}
|
|
#[test]
|
|
fn isolation_one() {
|
|
isolated();
|
|
}
|
|
#[test]
|
|
fn isolation_two() {
|
|
isolated();
|
|
}
|
|
#[test]
|
|
fn environment() {
|
|
for (key, _) in std::env::vars() {
|
|
assert!(
|
|
["CARGO_", "RUST_", "SAUSAGE_"]
|
|
.iter()
|
|
.any(|prefix| key.starts_with(prefix)),
|
|
"{key}"
|
|
);
|
|
}
|
|
assert!(std::env::var("PWD").is_err());
|
|
}
|
|
#[test]
|
|
fn late_response() {
|
|
use std::io::Write;
|
|
for id in 0..100 {
|
|
println!(
|
|
"\n{{\"jsonrpc\":\"2.0\",\"id\":{id},\"method\":\"core.log\",\"params\":{{}}}}"
|
|
);
|
|
}
|
|
std::io::stdout().flush().unwrap();
|
|
}
|
|
#[test]
|
|
#[ignore]
|
|
fn ignored_case() {
|
|
assert_eq!(1, 1);
|
|
}
|
|
}
|