//! 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 { 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); } }