cargo.build, the WASI test runner, the console, and core.notify ordering

- cargo.build (native): one source copy, tests under WASI, release
  build to /state/cargo/builds/<n>/, process groups killed on cancel.
  The host writes the SDK copy to /plugins/cargo/sdk/ at start.
- sausage test-runner: one instance per test, in parallel, with its own
  mount table (empty world, package folder at its path); answers
  core.lock, core.unlock, core.notify, core.log.
- The console: rustyline, notifications while typing, ls/cat through the
  process view, methods, ps, log, activate, revert, stop, kill.
- core.notify on stdout is handled in order, before the response.
This commit is contained in:
2026-10-23 01:42:19 +01:00
parent 0516158883
commit 8d058666dc
23 changed files with 1786 additions and 296 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
sausage-actor = "0.1"
sausage-plugin = "0.1"
[build-dependencies]
cc = "1"
+11 -11
View File
@@ -1,25 +1,25 @@
//! Adds two numbers with a C function: `{"type": "add", "a", "b"}` -> `{"type": "sum", "value"}`.
use sausage_actor::{json, messages, reply};
//! SDK plugin linked to a WASI C function.
unsafe extern "C" {
fn c_add(a: i32, b: i32) -> i32;
}
fn add(a: i32, b: i32) -> i32 {
unsafe { c_add(a, b) }
}
fn main() {
for message in messages() {
let (a, b) = (message.body["a"].as_i64().unwrap_or(0), message.body["b"].as_i64().unwrap_or(0));
let _ = reply(&message, json!({"type": "sum", "value": add(a as i32, b as i32)}));
struct Plugin;
#[sausage_plugin::rpc]
impl Plugin {
/// Add two fixed numbers through C.
fn sum(&self) -> sausage_plugin::Result<i32> {
Ok(add(2, 3))
}
}
fn main() {
sausage_plugin::run(Plugin);
}
#[cfg(test)]
mod tests {
#[test]
fn c_code_runs_on_the_host_target() {
fn c_code_runs_under_wasi() {
assert_eq!(super::add(2, 3), 5);
}
}
+6
View File
@@ -0,0 +1,6 @@
[package]
name = "cargo-hang"
version = "0.1.0"
edition = "2024"
[workspace]
+7
View File
@@ -0,0 +1,7 @@
//! A build script descendant that stays alive until the native call ends.
fn main() {
std::process::Command::new("sh")
.args(["-c", "echo $$ > child.pid; exec sleep 1000"])
.status()
.unwrap();
}
+1
View File
@@ -0,0 +1 @@
fn main() {}
+7 -3
View File
@@ -1,9 +1,13 @@
//! A panic must not prevent the runner from executing another test.
fn main() {}
#[cfg(test)]
mod tests {
#[test]
fn deliberate_unit_test_failure() {
assert_eq!(2 + 2, 5);
fn a_panics() {
panic!("deliberate WASI panic");
}
#[test]
fn z_survives() {
assert_eq!(2 + 2, 4);
}
}
+3
View File
@@ -3,4 +3,7 @@ name = "cargo-valid"
version = "0.1.0"
edition = "2024"
[dependencies]
sausage-plugin = "0.1"
[workspace]
+1
View File
@@ -0,0 +1 @@
fixture
+68 -3
View File
@@ -1,9 +1,74 @@
fn main() {}
//! 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 unit_tests_run_on_the_host_target() {
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 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"));
}
#[test]
#[ignore]
fn ignored_case() {
assert_eq!(1, 1);
}
}