2026-11-02 13:50:30 +00:00
|
|
|
use std::io::{BufRead, BufReader, Write};
|
|
|
|
|
use std::net::{Shutdown, SocketAddr, TcpStream};
|
|
|
|
|
use std::process::{Child, Command as ProcessCommand, Stdio};
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
2026-11-02 14:57:26 +00:00
|
|
|
use runa_client::{LocalClient, TcpClient};
|
2026-11-02 13:50:30 +00:00
|
|
|
use runa_protocol::{Command, MessageDto, PROTOCOL_VERSION, Reply, Request, Response};
|
|
|
|
|
|
|
|
|
|
struct ServerProcess(Child);
|
|
|
|
|
|
|
|
|
|
impl Drop for ServerProcess {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
let _ = self.0.kill();
|
|
|
|
|
let _ = self.0.wait();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-11-02 13:40:56 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn local_client_launches_server_and_operates_the_same_live_image() {
|
|
|
|
|
let mut client = LocalClient::spawn(env!("CARGO_BIN_EXE_runa-server"), &[] as &[&str]).unwrap();
|
|
|
|
|
|
|
|
|
|
let published = client
|
|
|
|
|
.request(Command::PublishBehavior {
|
|
|
|
|
name: "counter".to_owned(),
|
|
|
|
|
expected_revision: None,
|
2026-11-02 14:19:25 +00:00
|
|
|
source: "pub fn handle(state, kind, value, target_ref) { (state + value, []) }"
|
|
|
|
|
.to_owned(),
|
2026-11-02 13:40:56 +00:00
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(published.request_id, "runa-client-1");
|
|
|
|
|
assert!(matches!(published.result, Ok(Reply::Behavior(_))));
|
|
|
|
|
|
|
|
|
|
let spawned = client
|
|
|
|
|
.request(Command::Spawn {
|
|
|
|
|
behavior_name: "counter".to_owned(),
|
|
|
|
|
state: 5,
|
|
|
|
|
mailbox_capacity: 2,
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(spawned.request_id, "runa-client-2");
|
2026-11-02 14:19:25 +00:00
|
|
|
let actor_ref = match spawned.result {
|
|
|
|
|
Ok(Reply::ActorSpawned { actor_ref }) => actor_ref,
|
|
|
|
|
other => panic!("unexpected spawn response: {other:?}"),
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(actor_ref.len(), 21);
|
2026-11-02 13:40:56 +00:00
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.request(Command::Send {
|
2026-11-02 14:19:25 +00:00
|
|
|
actor_ref: actor_ref.clone(),
|
|
|
|
|
message: MessageDto {
|
|
|
|
|
kind: 0,
|
|
|
|
|
value: 7,
|
|
|
|
|
target_ref: None,
|
|
|
|
|
},
|
2026-11-02 13:40:56 +00:00
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
client
|
|
|
|
|
.request(Command::Tick {
|
|
|
|
|
ticks: 100,
|
|
|
|
|
instruction_budget: 10,
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
2026-11-02 14:19:25 +00:00
|
|
|
let inspected = client.request(Command::InspectActor { actor_ref }).unwrap();
|
2026-11-02 13:40:56 +00:00
|
|
|
assert!(matches!(
|
|
|
|
|
inspected.result,
|
|
|
|
|
Ok(Reply::Actor(actor)) if actor.state == 12
|
|
|
|
|
));
|
|
|
|
|
assert!(client.shutdown().unwrap().success());
|
|
|
|
|
}
|
2026-11-02 13:50:30 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn tcp_server_accepts_local_protocol_requests() {
|
|
|
|
|
let child = ProcessCommand::new(env!("CARGO_BIN_EXE_runa-server"))
|
|
|
|
|
.args(["--listen", "127.0.0.1:0"])
|
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
|
.stderr(Stdio::piped())
|
|
|
|
|
.spawn()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let mut server = ServerProcess(child);
|
|
|
|
|
let stderr = server.0.stderr.take().unwrap();
|
|
|
|
|
let mut stderr = BufReader::new(stderr);
|
|
|
|
|
let mut startup = String::new();
|
|
|
|
|
stderr.read_line(&mut startup).unwrap();
|
|
|
|
|
let address = startup
|
|
|
|
|
.strip_prefix("runa-server: listening on ")
|
|
|
|
|
.expect("server should report its bound address")
|
|
|
|
|
.trim()
|
|
|
|
|
.parse::<SocketAddr>()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let mut client = TcpStream::connect_timeout(&address, Duration::from_secs(2)).unwrap();
|
|
|
|
|
let request = Request {
|
|
|
|
|
version: PROTOCOL_VERSION,
|
|
|
|
|
request_id: "tcp-process-test".to_owned(),
|
|
|
|
|
command: Command::ListActors,
|
|
|
|
|
};
|
|
|
|
|
writeln!(client, "{}", serde_json::to_string(&request).unwrap()).unwrap();
|
|
|
|
|
client.shutdown(Shutdown::Write).unwrap();
|
|
|
|
|
|
|
|
|
|
let mut response_line = String::new();
|
|
|
|
|
BufReader::new(client)
|
|
|
|
|
.read_line(&mut response_line)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let response: Response = serde_json::from_str(&response_line).unwrap();
|
|
|
|
|
assert_eq!(response.request_id, "tcp-process-test");
|
|
|
|
|
assert_eq!(response.result, Ok(Reply::Actors(Vec::new())));
|
2026-11-02 14:57:26 +00:00
|
|
|
|
|
|
|
|
let mut first = TcpClient::connect(address).unwrap();
|
|
|
|
|
let mut second = TcpClient::connect(address).unwrap();
|
|
|
|
|
let actor_ref = match first
|
|
|
|
|
.request(Command::Spawn {
|
|
|
|
|
behavior_name: "counter".to_owned(),
|
|
|
|
|
state: 5,
|
|
|
|
|
mailbox_capacity: 2,
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.result
|
|
|
|
|
.unwrap()
|
|
|
|
|
{
|
|
|
|
|
Reply::ActorSpawned { actor_ref } => actor_ref,
|
|
|
|
|
other => panic!("unexpected spawn response: {other:?}"),
|
|
|
|
|
};
|
|
|
|
|
assert!(matches!(second.request(Command::InspectActor {
|
|
|
|
|
actor_ref: actor_ref.clone(),
|
|
|
|
|
}).unwrap().result, Ok(Reply::Actor(actor)) if actor.state == 5));
|
|
|
|
|
assert!(matches!(
|
|
|
|
|
first
|
|
|
|
|
.request(Command::RollbackBehavior {
|
|
|
|
|
name: "counter".to_owned(),
|
|
|
|
|
expected_revision: 1,
|
|
|
|
|
target_revision: 1,
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.result,
|
|
|
|
|
Ok(Reply::Behavior(behavior)) if behavior.revision == 2
|
|
|
|
|
));
|
|
|
|
|
drop(first);
|
|
|
|
|
drop(second);
|
|
|
|
|
|
|
|
|
|
let mut reconnected = TcpClient::connect(address).unwrap();
|
|
|
|
|
assert!(matches!(
|
|
|
|
|
reconnected
|
|
|
|
|
.request(Command::GetBehavior {
|
|
|
|
|
name: "counter".to_owned(),
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.result,
|
|
|
|
|
Ok(Reply::Behavior(behavior)) if behavior.revision == 2
|
|
|
|
|
));
|
|
|
|
|
reconnected
|
|
|
|
|
.request(Command::Send {
|
|
|
|
|
actor_ref: actor_ref.clone(),
|
|
|
|
|
message: MessageDto {
|
|
|
|
|
kind: 0,
|
|
|
|
|
value: 7,
|
|
|
|
|
target_ref: None,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.result
|
|
|
|
|
.unwrap();
|
|
|
|
|
reconnected
|
|
|
|
|
.request(Command::Tick {
|
|
|
|
|
ticks: 100,
|
|
|
|
|
instruction_budget: 10,
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.result
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(matches!(reconnected.request(Command::InspectActor {
|
|
|
|
|
actor_ref,
|
|
|
|
|
}).unwrap().result, Ok(Reply::Actor(actor)) if actor.state == 12));
|
2026-11-02 13:50:30 +00:00
|
|
|
}
|