cargo.build and the runner (review T22R): builds use the network again; core.unlock and core.notify in order under the runner; quiet test output; manifest preparation keeps [workspace], target tables, renamed and optional entries; cargo.rpc.discover; -32004 with data.output; retention of 10 builds; light, empty test worlds with an in-memory journal; only CARGO_/RUST_/SAUSAGE_ variables; stdin is null; the runner through --config. A compile cache that cannot be created is not fatal. Activation replaces plugin.wasm without a gap. Console and main: init ships the workspace Cargo.lock and creates the world atomically; each console command runs in its own task, so ps, stop, and kill work during a slow call; SAUSAGE_HEADLESS; the terminal mode is restored; signals during boot; chat text is not taken as a command. Tests: 20 more tests for PROTOCOL.md sections 2 to 10; the cargo tests choose the libtest binary from cargo's JSON output and time out.
121 lines
4.7 KiB
Rust
121 lines
4.7 KiB
Rust
//! Raw discovery and lifecycle guest for activation protocol tests.
|
|
use serde_json::{Value, json};
|
|
use std::io::{self, BufRead, Write};
|
|
/// Emits one flushed JSON-RPC response.
|
|
fn send(value: Value) {
|
|
println!("{value}");
|
|
io::stdout().flush().unwrap();
|
|
}
|
|
/// Calls a provider after activation releases an old consumer process.
|
|
fn deferred(request: &Value, plugin: &str, lines: &mut impl Iterator<Item = io::Result<String>>) {
|
|
send(
|
|
json!({"jsonrpc":"2.0","id":request["id"],"result":std::env::var("SAUSAGE_PID").unwrap()}),
|
|
);
|
|
while !std::path::Path::new(&format!("/state/{plugin}/release-call")).exists() {
|
|
std::thread::sleep(std::time::Duration::from_millis(1));
|
|
}
|
|
send(json!({"jsonrpc":"2.0","id":"deferred","method":"notes.legacy","params":{}}));
|
|
for line in lines {
|
|
let response: Value = serde_json::from_str(&line.unwrap()).unwrap();
|
|
if response["id"] == "deferred" {
|
|
std::fs::write(format!("/state/{plugin}/call-result"), response.to_string()).unwrap();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/// Answers a method that version two removes from its interface.
|
|
fn legacy(request: &Value, document: &Value, version: &str) {
|
|
if document["methods"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|m| m["name"] == "legacy")
|
|
{
|
|
send(json!({"jsonrpc":"2.0","id":request["id"],"result":version}));
|
|
} else {
|
|
send(
|
|
json!({"jsonrpc":"2.0","id":request["id"],"error":{"code":-32601,"message":"legacy removed","data":{"version":version}}}),
|
|
);
|
|
}
|
|
}
|
|
/// Reads a version-specific discovery document without the SDK.
|
|
fn main() {
|
|
let version = env!("FIXTURE_VERSION");
|
|
let plugin = std::env::var("SAUSAGE_PLUGIN").unwrap();
|
|
let mut lines = io::stdin().lock().lines();
|
|
let request: Value = serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();
|
|
let document: Value =
|
|
serde_json::from_slice(&std::fs::read(format!("/state/{plugin}/{version}.json")).unwrap())
|
|
.unwrap();
|
|
let result = match request["method"].as_str().unwrap() {
|
|
"deferred" => {
|
|
deferred(&request, &plugin, &mut lines);
|
|
return;
|
|
}
|
|
"legacy" => {
|
|
legacy(&request, &document, version);
|
|
return;
|
|
}
|
|
"rpc.discover" => {
|
|
if document["x-test-wait"] == true {
|
|
std::fs::write(format!("/state/{plugin}/discover-started"), "ready").unwrap();
|
|
while !std::path::Path::new(&format!("/state/{plugin}/discover-release")).exists() {
|
|
std::thread::sleep(std::time::Duration::from_millis(1));
|
|
}
|
|
}
|
|
document
|
|
}
|
|
"read" => {
|
|
let text =
|
|
std::fs::read_to_string(request["params"]["path"].as_str().unwrap()).unwrap();
|
|
serde_json::from_str(&text).unwrap_or(json!(text))
|
|
}
|
|
"rpc.retired" => {
|
|
std::fs::write(
|
|
format!("/state/{plugin}/retired"),
|
|
request["params"]["previous"].as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
json!({"retired":version})
|
|
}
|
|
"serve" | "hold" => {
|
|
send(json!({"jsonrpc":"2.0","id":request["id"],"result":{"version":version}}));
|
|
if request["method"] == "serve" {
|
|
let exclusive = format!("/run/{plugin}-service");
|
|
if std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(&exclusive)
|
|
.is_err()
|
|
{
|
|
std::fs::write(format!("/state/{plugin}/duplicate-service"), "duplicate")
|
|
.unwrap();
|
|
}
|
|
std::fs::write(format!("/state/{plugin}/service"), version).unwrap();
|
|
}
|
|
for _ in lines {}
|
|
if request["method"] == "serve" {
|
|
let _ = std::fs::remove_file(format!("/run/{plugin}-service"));
|
|
}
|
|
std::fs::write(format!("/state/{plugin}/ended-{version}"), "ended").unwrap();
|
|
return;
|
|
}
|
|
_ => json!({"version":version}),
|
|
};
|
|
send(json!({"jsonrpc":"2.0","id":request["id"],"result":result}));
|
|
if request["method"] == "rpc.discover" && result["x-test-linger"] == true {
|
|
if result["x-test-stubborn"] == true {
|
|
while !std::path::Path::new(&format!("/state/{plugin}/discovery-exit")).exists() {
|
|
std::thread::sleep(std::time::Duration::from_millis(1));
|
|
}
|
|
} else {
|
|
for _ in lines {}
|
|
}
|
|
std::fs::write(
|
|
format!("/state/{plugin}/discovery-ended-{version}"),
|
|
"ended",
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|