|
|
|
|
@ -1,21 +1,26 @@
|
|
|
|
|
use std::io::{self, BufRead, Write};
|
|
|
|
|
use std::num::NonZeroUsize;
|
|
|
|
|
|
|
|
|
|
use runa_actor::{ActorId, Message};
|
|
|
|
|
use runa_image_api::ImageApi;
|
|
|
|
|
use runa_rune::ExperimentResult;
|
|
|
|
|
use runa_protocol::{Command, MessageDto, PROTOCOL_VERSION, Reply, Request, Response};
|
|
|
|
|
use runa_server::Server;
|
|
|
|
|
|
|
|
|
|
const MAILBOX_CAPACITY: usize = 64;
|
|
|
|
|
const DEFAULT_TICK_BUDGET: usize = 32;
|
|
|
|
|
const DEFAULT_BEHAVIOR: &str = "counter";
|
|
|
|
|
|
|
|
|
|
fn main() -> ExperimentResult<()> {
|
|
|
|
|
let mut image = ImageApi::new();
|
|
|
|
|
let behavior = image.publish_behavior(
|
|
|
|
|
DEFAULT_BEHAVIOR,
|
|
|
|
|
None,
|
|
|
|
|
include_str!("../../../runa-std/actors/counter.rn"),
|
|
|
|
|
)?;
|
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
let mut server = Server::new();
|
|
|
|
|
let behavior = match dispatch(
|
|
|
|
|
&mut server,
|
|
|
|
|
Command::PublishBehavior {
|
|
|
|
|
name: DEFAULT_BEHAVIOR.to_owned(),
|
|
|
|
|
expected_revision: None,
|
|
|
|
|
source: include_str!("../../../runa-std/actors/counter.rn").to_owned(),
|
|
|
|
|
},
|
|
|
|
|
)? {
|
|
|
|
|
Reply::Behavior(behavior) => behavior,
|
|
|
|
|
other => return Err(format!("unexpected publish response: {other:?}").into()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
println!("Runa — Rune Actors");
|
|
|
|
|
println!(
|
|
|
|
|
@ -26,7 +31,7 @@ fn main() -> ExperimentResult<()> {
|
|
|
|
|
run_repl(
|
|
|
|
|
io::stdin().lock(),
|
|
|
|
|
io::stdout().lock(),
|
|
|
|
|
&mut image,
|
|
|
|
|
&mut server,
|
|
|
|
|
DEFAULT_BEHAVIOR,
|
|
|
|
|
)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
@ -35,7 +40,7 @@ fn main() -> ExperimentResult<()> {
|
|
|
|
|
fn run_repl<R: BufRead, W: Write>(
|
|
|
|
|
mut input: R,
|
|
|
|
|
mut output: W,
|
|
|
|
|
image: &mut ImageApi,
|
|
|
|
|
server: &mut Server,
|
|
|
|
|
default_behavior: &str,
|
|
|
|
|
) -> io::Result<()> {
|
|
|
|
|
let mut line = String::new();
|
|
|
|
|
@ -54,33 +59,48 @@ fn run_repl<R: BufRead, W: Write>(
|
|
|
|
|
let args: Vec<_> = words.collect();
|
|
|
|
|
let result = match command {
|
|
|
|
|
"help" => print_help(&mut output),
|
|
|
|
|
"spawn" => spawn_command(&mut output, &args, default_behavior, image),
|
|
|
|
|
"behaviors" if args.is_empty() => {
|
|
|
|
|
for behavior in image.behaviors() {
|
|
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"{} revision {} ({})",
|
|
|
|
|
behavior.name, behavior.revision, behavior.current
|
|
|
|
|
)?;
|
|
|
|
|
"spawn" => spawn_command(&mut output, &args, default_behavior, server),
|
|
|
|
|
"behaviors" if args.is_empty() => match dispatch(server, Command::GetBehaviors) {
|
|
|
|
|
Ok(Reply::Behaviors(behaviors)) => {
|
|
|
|
|
for behavior in behaviors {
|
|
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"{} revision {} ({})",
|
|
|
|
|
behavior.name, behavior.revision, behavior.current_implementation_id
|
|
|
|
|
)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Ok(other) => unexpected_reply(&mut output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
},
|
|
|
|
|
"behaviors" => writeln!(output, "error: usage: behaviors"),
|
|
|
|
|
"publish" => publish_command(&mut output, &args, image),
|
|
|
|
|
"history" => history_command(&mut output, &args, image),
|
|
|
|
|
"rollback" => rollback_command(&mut output, &args, image),
|
|
|
|
|
"install" => install_command(&mut output, &args, image),
|
|
|
|
|
"actors" if args.is_empty() => {
|
|
|
|
|
for actor in image.actors() {
|
|
|
|
|
writeln!(output, "{actor:#?}")?;
|
|
|
|
|
"publish" => publish_command(&mut output, &args, server),
|
|
|
|
|
"history" => history_command(&mut output, &args, server),
|
|
|
|
|
"rollback" => rollback_command(&mut output, &args, server),
|
|
|
|
|
"install" => install_command(&mut output, &args, server),
|
|
|
|
|
"actors" if args.is_empty() => match dispatch(server, Command::ListActors) {
|
|
|
|
|
Ok(Reply::Actors(actors)) => {
|
|
|
|
|
for actor in actors {
|
|
|
|
|
writeln!(output, "{actor:#?}")?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Ok(other) => unexpected_reply(&mut output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
},
|
|
|
|
|
"actors" => writeln!(output, "error: usage: actors"),
|
|
|
|
|
"send" => match parse_send_args(&args) {
|
|
|
|
|
Ok((actor, kind, value)) => {
|
|
|
|
|
match image.send(ActorId(actor), Message { kind, value }) {
|
|
|
|
|
Ok(()) => writeln!(output, "message queued"),
|
|
|
|
|
match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::Send {
|
|
|
|
|
actor_id: actor,
|
|
|
|
|
message: MessageDto { kind, value },
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::Acknowledged) => writeln!(output, "message queued"),
|
|
|
|
|
Ok(other) => unexpected_reply(&mut output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -88,32 +108,56 @@ fn run_repl<R: BufRead, W: Write>(
|
|
|
|
|
},
|
|
|
|
|
"tick" => match parse_tick_args(&args) {
|
|
|
|
|
Ok((count, budget)) => {
|
|
|
|
|
image.tick(count, budget);
|
|
|
|
|
writeln!(output, "ran {count} scheduler tick(s)")
|
|
|
|
|
match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::Tick {
|
|
|
|
|
ticks: count,
|
|
|
|
|
instruction_budget: budget.get(),
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::TickCompleted { ticks }) => {
|
|
|
|
|
writeln!(output, "ran {ticks} scheduler tick(s)")
|
|
|
|
|
}
|
|
|
|
|
Ok(other) => unexpected_reply(&mut output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
},
|
|
|
|
|
"inspect" => match parse_exact::<u64, 1>(&args) {
|
|
|
|
|
Ok([id]) => match image.inspect(ActorId(id)) {
|
|
|
|
|
Ok(actor) => writeln!(output, "{actor:#?}"),
|
|
|
|
|
Ok([id]) => match dispatch(server, Command::InspectActor { actor_id: id }) {
|
|
|
|
|
Ok(Reply::Actor(actor)) => writeln!(output, "{actor:#?}"),
|
|
|
|
|
Ok(other) => unexpected_reply(&mut output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
},
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
},
|
|
|
|
|
"events" if args.is_empty() => {
|
|
|
|
|
for event in image.events() {
|
|
|
|
|
writeln!(output, "{event:#?}")?;
|
|
|
|
|
"events" if args.is_empty() => match dispatch(server, Command::GetEvents) {
|
|
|
|
|
Ok(Reply::Events(events)) => {
|
|
|
|
|
for event in events {
|
|
|
|
|
writeln!(output, "{event:#?}")?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Ok(other) => unexpected_reply(&mut output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
},
|
|
|
|
|
"events" => writeln!(output, "error: usage: events"),
|
|
|
|
|
"stop" => lifecycle_command(&mut output, &args, |image, id| image.stop(id), image),
|
|
|
|
|
"restart" => {
|
|
|
|
|
lifecycle_command(&mut output, &args, |image, id| image.restart(id), image)
|
|
|
|
|
}
|
|
|
|
|
"pause" => lifecycle_command(&mut output, &args, |image, id| image.pause(id), image),
|
|
|
|
|
"resume" => lifecycle_command(&mut output, &args, |image, id| image.resume(id), image),
|
|
|
|
|
"cancel" => lifecycle_command(&mut output, &args, |image, id| image.cancel(id), image),
|
|
|
|
|
"stop" => lifecycle_command(&mut output, &args, server, |actor_id| {
|
|
|
|
|
Command::StopActor { actor_id }
|
|
|
|
|
}),
|
|
|
|
|
"restart" => lifecycle_command(&mut output, &args, server, |actor_id| {
|
|
|
|
|
Command::RestartActor { actor_id }
|
|
|
|
|
}),
|
|
|
|
|
"pause" => lifecycle_command(&mut output, &args, server, |actor_id| {
|
|
|
|
|
Command::PauseActor { actor_id }
|
|
|
|
|
}),
|
|
|
|
|
"resume" => lifecycle_command(&mut output, &args, server, |actor_id| {
|
|
|
|
|
Command::ResumeActor { actor_id }
|
|
|
|
|
}),
|
|
|
|
|
"cancel" => lifecycle_command(&mut output, &args, server, |actor_id| {
|
|
|
|
|
Command::CancelActor { actor_id }
|
|
|
|
|
}),
|
|
|
|
|
"quit" | "exit" if args.is_empty() => break,
|
|
|
|
|
"quit" | "exit" => writeln!(output, "error: usage: quit"),
|
|
|
|
|
_ => writeln!(output, "error: unknown command `{command}`; type `help`"),
|
|
|
|
|
@ -126,12 +170,13 @@ fn run_repl<R: BufRead, W: Write>(
|
|
|
|
|
fn lifecycle_command<W: Write>(
|
|
|
|
|
output: &mut W,
|
|
|
|
|
args: &[&str],
|
|
|
|
|
operation: impl FnOnce(&mut ImageApi, ActorId) -> ExperimentResult<()>,
|
|
|
|
|
image: &mut ImageApi,
|
|
|
|
|
server: &mut Server,
|
|
|
|
|
operation: impl FnOnce(u64) -> Command,
|
|
|
|
|
) -> io::Result<()> {
|
|
|
|
|
match parse_exact::<u64, 1>(args) {
|
|
|
|
|
Ok([id]) => match operation(image, ActorId(id)) {
|
|
|
|
|
Ok(()) => writeln!(output, "ok"),
|
|
|
|
|
Ok([id]) => match dispatch(server, operation(id)) {
|
|
|
|
|
Ok(Reply::Acknowledged) => writeln!(output, "ok"),
|
|
|
|
|
Ok(other) => unexpected_reply(output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
},
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
@ -142,7 +187,7 @@ fn spawn_command<W: Write>(
|
|
|
|
|
output: &mut W,
|
|
|
|
|
args: &[&str],
|
|
|
|
|
default_behavior: &str,
|
|
|
|
|
image: &mut ImageApi,
|
|
|
|
|
server: &mut Server,
|
|
|
|
|
) -> io::Result<()> {
|
|
|
|
|
let (state_arg, behavior_name) = match args {
|
|
|
|
|
[state] => (*state, default_behavior),
|
|
|
|
|
@ -158,28 +203,37 @@ fn spawn_command<W: Write>(
|
|
|
|
|
Ok(state) => state,
|
|
|
|
|
Err(_) => return writeln!(output, "error: invalid initial state: `{state_arg}`"),
|
|
|
|
|
};
|
|
|
|
|
match image.spawn(
|
|
|
|
|
behavior_name,
|
|
|
|
|
state,
|
|
|
|
|
NonZeroUsize::new(MAILBOX_CAPACITY).expect("capacity is nonzero"),
|
|
|
|
|
) {
|
|
|
|
|
Ok(id) => writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"spawned {id:?} with `{behavior_name}` revision {}",
|
|
|
|
|
image
|
|
|
|
|
.behavior(behavior_name)
|
|
|
|
|
.expect("spawn resolved behavior")
|
|
|
|
|
.revision
|
|
|
|
|
),
|
|
|
|
|
let response = dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::Spawn {
|
|
|
|
|
behavior_name: behavior_name.to_owned(),
|
|
|
|
|
state,
|
|
|
|
|
mailbox_capacity: MAILBOX_CAPACITY,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
match response {
|
|
|
|
|
Ok(Reply::ActorSpawned { actor_id }) => {
|
|
|
|
|
let revision = match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::GetBehavior {
|
|
|
|
|
name: behavior_name.to_owned(),
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::Behavior(behavior)) => behavior.revision,
|
|
|
|
|
Ok(other) => return unexpected_reply(output, other),
|
|
|
|
|
Err(error) => return writeln!(output, "error: {error}"),
|
|
|
|
|
};
|
|
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"spawned ActorId({actor_id}) with `{behavior_name}` revision {revision}"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
Ok(other) => unexpected_reply(output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn publish_command<W: Write>(
|
|
|
|
|
output: &mut W,
|
|
|
|
|
args: &[&str],
|
|
|
|
|
image: &mut ImageApi,
|
|
|
|
|
) -> io::Result<()> {
|
|
|
|
|
fn publish_command<W: Write>(output: &mut W, args: &[&str], server: &mut Server) -> io::Result<()> {
|
|
|
|
|
let [name, expected_revision, source_path] = args else {
|
|
|
|
|
return writeln!(
|
|
|
|
|
output,
|
|
|
|
|
@ -200,23 +254,38 @@ fn publish_command<W: Write>(
|
|
|
|
|
return writeln!(output, "error: could not read `{source_path}`: {error}");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let behavior = match image.publish_behavior(name, expected_revision, &source) {
|
|
|
|
|
Ok(behavior) => behavior,
|
|
|
|
|
let behavior = match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::PublishBehavior {
|
|
|
|
|
name: (*name).to_owned(),
|
|
|
|
|
expected_revision,
|
|
|
|
|
source,
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::Behavior(behavior)) => behavior,
|
|
|
|
|
Ok(other) => return unexpected_reply(output, other),
|
|
|
|
|
Err(error) => return writeln!(output, "error: {error}"),
|
|
|
|
|
};
|
|
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"published `{}` revision {} ({})",
|
|
|
|
|
behavior.name, behavior.revision, behavior.current
|
|
|
|
|
behavior.name, behavior.revision, behavior.current_implementation_id
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn history_command<W: Write>(output: &mut W, args: &[&str], image: &ImageApi) -> io::Result<()> {
|
|
|
|
|
fn history_command<W: Write>(output: &mut W, args: &[&str], server: &mut Server) -> io::Result<()> {
|
|
|
|
|
let [name] = args else {
|
|
|
|
|
return writeln!(output, "error: usage: history <behavior-name>");
|
|
|
|
|
};
|
|
|
|
|
let Some(behavior) = image.behavior(name) else {
|
|
|
|
|
return writeln!(output, "error: unknown behavior `{name}`");
|
|
|
|
|
let behavior = match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::GetBehavior {
|
|
|
|
|
name: (*name).to_owned(),
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::Behavior(behavior)) => behavior,
|
|
|
|
|
Ok(other) => return unexpected_reply(output, other),
|
|
|
|
|
Err(error) => return writeln!(output, "error: {error}"),
|
|
|
|
|
};
|
|
|
|
|
for version in behavior.history {
|
|
|
|
|
let current = if version.revision == behavior.revision {
|
|
|
|
|
@ -236,7 +305,7 @@ fn history_command<W: Write>(output: &mut W, args: &[&str], image: &ImageApi) ->
|
|
|
|
|
fn rollback_command<W: Write>(
|
|
|
|
|
output: &mut W,
|
|
|
|
|
args: &[&str],
|
|
|
|
|
image: &mut ImageApi,
|
|
|
|
|
server: &mut Server,
|
|
|
|
|
) -> io::Result<()> {
|
|
|
|
|
let [name, expected_revision, target_revision] = args else {
|
|
|
|
|
return writeln!(
|
|
|
|
|
@ -252,41 +321,74 @@ fn rollback_command<W: Write>(
|
|
|
|
|
Ok(revision) => revision,
|
|
|
|
|
Err(_) => return writeln!(output, "error: invalid target revision"),
|
|
|
|
|
};
|
|
|
|
|
match image.rollback_behavior(name, expected_revision, target_revision) {
|
|
|
|
|
Ok(behavior) => writeln!(
|
|
|
|
|
match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::RollbackBehavior {
|
|
|
|
|
name: (*name).to_owned(),
|
|
|
|
|
expected_revision,
|
|
|
|
|
target_revision,
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::Behavior(behavior)) => writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"rolled back `{}` to implementation {} as revision {}",
|
|
|
|
|
behavior.name, behavior.current, behavior.revision
|
|
|
|
|
behavior.name, behavior.current_implementation_id, behavior.revision
|
|
|
|
|
),
|
|
|
|
|
Ok(other) => unexpected_reply(output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn install_command<W: Write>(
|
|
|
|
|
output: &mut W,
|
|
|
|
|
args: &[&str],
|
|
|
|
|
image: &mut ImageApi,
|
|
|
|
|
) -> io::Result<()> {
|
|
|
|
|
fn install_command<W: Write>(output: &mut W, args: &[&str], server: &mut Server) -> io::Result<()> {
|
|
|
|
|
let [actor_id, name] = args else {
|
|
|
|
|
return writeln!(output, "error: usage: install <actor-id> <behavior-name>");
|
|
|
|
|
};
|
|
|
|
|
let actor_id = match actor_id.parse::<u64>() {
|
|
|
|
|
Ok(id) => ActorId(id),
|
|
|
|
|
Ok(id) => id,
|
|
|
|
|
Err(_) => return writeln!(output, "error: invalid actor ID"),
|
|
|
|
|
};
|
|
|
|
|
match image.install(actor_id, name) {
|
|
|
|
|
Ok(implementation) => {
|
|
|
|
|
let behavior = image.behavior(name).expect("install resolved behavior");
|
|
|
|
|
let implementation = match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::InstallBehavior {
|
|
|
|
|
actor_id,
|
|
|
|
|
behavior_name: (*name).to_owned(),
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::BehaviorInstalled { implementation_id }) => implementation_id,
|
|
|
|
|
Ok(other) => return unexpected_reply(output, other),
|
|
|
|
|
Err(error) => return writeln!(output, "error: {error}"),
|
|
|
|
|
};
|
|
|
|
|
match dispatch(
|
|
|
|
|
server,
|
|
|
|
|
Command::GetBehavior {
|
|
|
|
|
name: (*name).to_owned(),
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
Ok(Reply::Behavior(behavior)) => {
|
|
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"installed `{}` revision {} on actor {} ({implementation})",
|
|
|
|
|
behavior.name, behavior.revision, actor_id.0
|
|
|
|
|
behavior.name, behavior.revision, actor_id
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
Ok(other) => unexpected_reply(output, other),
|
|
|
|
|
Err(error) => writeln!(output, "error: {error}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn dispatch(server: &mut Server, command: Command) -> Result<Reply, String> {
|
|
|
|
|
let response: Response = server.dispatch(Request {
|
|
|
|
|
version: PROTOCOL_VERSION,
|
|
|
|
|
request_id: "runa-cli".to_owned(),
|
|
|
|
|
command,
|
|
|
|
|
});
|
|
|
|
|
response.result.map_err(|error| error.message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn unexpected_reply<W: Write>(output: &mut W, reply: Reply) -> io::Result<()> {
|
|
|
|
|
writeln!(output, "error: unexpected protocol response: {reply:?}")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_exact<T: std::str::FromStr, const N: usize>(args: &[&str]) -> Result<[T; N], String> {
|
|
|
|
|
if args.len() != N {
|
|
|
|
|
return Err(format!("expected {N} argument(s), got {}", args.len()));
|
|
|
|
|
@ -371,38 +473,43 @@ fn print_help<W: Write>(output: &mut W) -> io::Result<()> {
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
fn image() -> ExperimentResult<ImageApi> {
|
|
|
|
|
let mut image = ImageApi::new();
|
|
|
|
|
image.publish_behavior(
|
|
|
|
|
DEFAULT_BEHAVIOR,
|
|
|
|
|
None,
|
|
|
|
|
include_str!("../../../runa-std/actors/counter.rn"),
|
|
|
|
|
)?;
|
|
|
|
|
Ok(image)
|
|
|
|
|
fn server() -> Server {
|
|
|
|
|
let mut server = Server::new();
|
|
|
|
|
dispatch(
|
|
|
|
|
&mut server,
|
|
|
|
|
Command::PublishBehavior {
|
|
|
|
|
name: DEFAULT_BEHAVIOR.to_owned(),
|
|
|
|
|
expected_revision: None,
|
|
|
|
|
source: include_str!("../../../runa-std/actors/counter.rn").to_owned(),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.expect("default behavior publishes");
|
|
|
|
|
server
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn repl_drives_actor_messages_and_inspection() -> ExperimentResult<()> {
|
|
|
|
|
let mut image = image()?;
|
|
|
|
|
fn repl_drives_actor_messages_and_inspection() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
let mut server = server();
|
|
|
|
|
let input = io::Cursor::new(
|
|
|
|
|
"spawn 0\nspawn 10\nsend 1 1 2\ntick 100\ninspect 1\ninspect 2\nevents\nquit\n",
|
|
|
|
|
);
|
|
|
|
|
let mut output = Vec::new();
|
|
|
|
|
run_repl(input, &mut output, &mut image, DEFAULT_BEHAVIOR)?;
|
|
|
|
|
run_repl(input, &mut output, &mut server, DEFAULT_BEHAVIOR)?;
|
|
|
|
|
let output = String::from_utf8(output)?;
|
|
|
|
|
assert!(output.contains("ActorId(\n 1,"));
|
|
|
|
|
assert!(output.contains("ActorId(\n 2,"));
|
|
|
|
|
assert!(output.contains("actor_id: 1"));
|
|
|
|
|
assert!(output.contains("actor_id: 2"));
|
|
|
|
|
assert!(output.contains("state: 15"));
|
|
|
|
|
assert!(output.contains("Committed"));
|
|
|
|
|
assert!(output.contains("Committed {"));
|
|
|
|
|
assert!(output.contains("actor_id: 2"));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn repl_reports_bad_input_and_continues() -> ExperimentResult<()> {
|
|
|
|
|
let mut image = image()?;
|
|
|
|
|
fn repl_reports_bad_input_and_continues() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
let mut server = server();
|
|
|
|
|
let input = io::Cursor::new("spawn nope\nsend 999 0 1\ntick 1 0\nunknown\nquit\n");
|
|
|
|
|
let mut output = Vec::new();
|
|
|
|
|
run_repl(input, &mut output, &mut image, DEFAULT_BEHAVIOR)?;
|
|
|
|
|
run_repl(input, &mut output, &mut server, DEFAULT_BEHAVIOR)?;
|
|
|
|
|
let output = String::from_utf8(output)?;
|
|
|
|
|
assert!(output.contains("invalid initial state"));
|
|
|
|
|
assert!(output.contains("unknown actor"));
|
|
|
|
|
@ -412,8 +519,9 @@ mod tests {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn repl_manages_named_behavior_revisions_and_installs_them() -> ExperimentResult<()> {
|
|
|
|
|
let mut image = image()?;
|
|
|
|
|
fn repl_manages_named_behavior_revisions_and_installs_them()
|
|
|
|
|
-> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
let mut server = server();
|
|
|
|
|
let source_path = concat!(
|
|
|
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
|
|
|
"/../../runa-std/actors/counter.rn"
|
|
|
|
|
@ -422,7 +530,7 @@ mod tests {
|
|
|
|
|
"behaviors\npublish counter 1 {source_path}\nhistory counter\nrollback counter 2 1\nspawn 0 counter\ninstall 1 counter\nsend 1 0 1\ntick 1 1\ncancel 1\npause 1\nresume 1\ninspect 1\nevents\nquit\n"
|
|
|
|
|
));
|
|
|
|
|
let mut output = Vec::new();
|
|
|
|
|
run_repl(input, &mut output, &mut image, DEFAULT_BEHAVIOR)?;
|
|
|
|
|
run_repl(input, &mut output, &mut server, DEFAULT_BEHAVIOR)?;
|
|
|
|
|
let output = String::from_utf8(output)?;
|
|
|
|
|
assert!(output.contains("published `counter` revision 2"));
|
|
|
|
|
assert!(output.contains("revision 1:"));
|
|
|
|
|
@ -430,14 +538,24 @@ mod tests {
|
|
|
|
|
assert!(output.contains("rolled back `counter`"));
|
|
|
|
|
assert!(output.contains("spawned ActorId(1) with `counter` revision 3"));
|
|
|
|
|
assert!(output.contains("installed `counter` revision 3 on actor 1"));
|
|
|
|
|
assert!(output.contains("Paused("));
|
|
|
|
|
assert!(output.contains("Resumed("));
|
|
|
|
|
assert!(output.contains("InvocationCancelled("));
|
|
|
|
|
assert_eq!(image.behavior(DEFAULT_BEHAVIOR).unwrap().revision, 3);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
image.inspect(ActorId(1))?.implementation,
|
|
|
|
|
image.behavior(DEFAULT_BEHAVIOR).unwrap().current
|
|
|
|
|
);
|
|
|
|
|
assert!(output.contains("Paused {"));
|
|
|
|
|
assert!(output.contains("Resumed {"));
|
|
|
|
|
assert!(output.contains("InvocationCancelled {"));
|
|
|
|
|
let behavior = match dispatch(
|
|
|
|
|
&mut server,
|
|
|
|
|
Command::GetBehavior {
|
|
|
|
|
name: DEFAULT_BEHAVIOR.to_owned(),
|
|
|
|
|
},
|
|
|
|
|
)? {
|
|
|
|
|
Reply::Behavior(behavior) => behavior,
|
|
|
|
|
other => return Err(format!("unexpected response: {other:?}").into()),
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(behavior.revision, 3);
|
|
|
|
|
let actor = match dispatch(&mut server, Command::InspectActor { actor_id: 1 })? {
|
|
|
|
|
Reply::Actor(actor) => actor,
|
|
|
|
|
other => return Err(format!("unexpected response: {other:?}").into()),
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(actor.implementation_id, behavior.current_implementation_id);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|