Skip to content

Commit

Permalink
update update calls; support e2e testing (#60)
Browse files Browse the repository at this point in the history
* update update calls; support e2e testing

* fix

* fix serialization mismatch

* catch edge cases for graphics output

* consistent skip events

* service api in docs

* write graphics

* cli control as -G
  • Loading branch information
matthewhammer authored Jan 12, 2021
1 parent c08f39d commit c26ac37
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 61 deletions.
10 changes: 0 additions & 10 deletions LOC

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ the mini-terminal update-view service protocol, given below in Candid syntax (el

```
service : {
update: (vec EventInfo) -> () oneway;
view: (Dim, vec EventInfo) -> (Graphics) query;
update: (vec EventInfo, GraphicsRequest) -> (vec Graphics);
}
```

Expand Down
4 changes: 2 additions & 2 deletions scripts/MirrorGarden-demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ dfx canister create MirrorGarden &&\
dfx build MirrorGarden &&\
dfx canister install MirrorGarden

ic-mt connect 127.0.0.1:8000 `dfx canister id MirrorGarden` --user '("Alice", (100, 200, 200))' &
ic-mt connect 127.0.0.1:8000 `dfx canister id MirrorGarden` --user '("Bob", (200, 100, 200))' &
ic-mt ${1} connect 127.0.0.1:8000 `dfx canister id MirrorGarden` --user '("Alice", (100, 200, 200))' &
ic-mt ${1} connect 127.0.0.1:8000 `dfx canister id MirrorGarden` --user '("Bob", (200, 100, 200))' &
echo
echo Mirror Garden 2-user demo, started.
echo
10 changes: 10 additions & 0 deletions scripts/decode-graphics.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh

if [ -f "${1}" ]; then
didc decode `cat ${1}` -d service.did -t '(vec Graphics)'
else
echo "Error within ${0}: File not found:"
echo " - Could not find event file ${1}"
echo
echo "usage: ${0} <icmt-events-filename>"
fi
File renamed without changes.
14 changes: 13 additions & 1 deletion service.did
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ type KeyInfo =
meta: bool;
shift: bool;
};
type GraphicsRequest_2 =
variant {
all: Dim_2;
last: Dim_2;
none;
};
type GraphicsRequest = GraphicsRequest_2;
type Graphics = Result;
type Fill =
variant {
Expand All @@ -75,6 +82,11 @@ type EventInfo_2 =
type EventInfo = EventInfo_2;
type Event =
variant {
clipBoard: text;
fileRead: record {
content: text;
path: text;
};
keyDown: vec KeyInfo;
mouseDown: Pos;
quit;
Expand Down Expand Up @@ -103,6 +115,6 @@ type Color =
nat;
};
service : {
update: (vec EventInfo) -> () oneway;
update: (vec EventInfo, GraphicsRequest) -> (vec Graphics);
view: (Dim, vec EventInfo) -> (Graphics) query;
}
109 changes: 75 additions & 34 deletions src/bin/ic-mt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async fn do_redraw<'a, T1: RenderTarget>(
window_canvas: &mut Canvas<T1>,
file_canvas: &mut Canvas<Surface<'a>>,
bmp_paths: &mut Vec<String>,
data: &Option<graphics::Result>,
data: &graphics::Result,
) -> IcmtResult<()> {
if !cli.no_window {
draw(window_canvas, window_dim, data).await?;
Expand All @@ -137,7 +137,7 @@ async fn do_redraw<'a, T1: RenderTarget>(
async fn do_view_task(
cfg: ConnectCfg,
remote_in: mpsc::Receiver<Option<(graphics::Dim, Vec<event::EventInfo>)>>,
remote_out: mpsc::Sender<Option<graphics::Result>>,
remote_out: mpsc::Sender<graphics::Result>,
) -> IcmtResult<()> {
/* Create our own agent here since we cannot Send it here from the main thread. */
let canister_id = Principal::from_text(cfg.canister_id.clone()).unwrap();
Expand All @@ -154,8 +154,9 @@ async fn do_view_task(
match events {
None => return Ok(()),
Some((window_dim, events)) => {
let rr = service_call(&ctx, ServiceCall::View(window_dim, events)).await?;
remote_out.send(rr)?;
let mut rr = service_call(&ctx, ServiceCall::View(window_dim, events)).await?;
assert_eq!(rr.len(), 1);
remote_out.send(rr.remove(0))?;
}
}
}
Expand All @@ -164,7 +165,7 @@ async fn do_view_task(
async fn do_update_task(
cfg: ConnectCfg,
remote_in: mpsc::Receiver<ServiceCall>,
remote_out: mpsc::Sender<()>,
remote_out: mpsc::Sender<Vec<graphics::Result>>,
) -> IcmtResult<()> {
/* Create our own agent here since we cannot Send it here from the main thread. */
let canister_id = Principal::from_text(cfg.canister_id.clone()).unwrap();
Expand All @@ -179,8 +180,8 @@ async fn do_update_task(
if let ServiceCall::FlushQuit = sc {
return Ok(());
};
service_call(&ctx, sc).await?;
remote_out.send(()).unwrap();
let r = service_call(&ctx, sc).await?;
remote_out.send(r).unwrap();
}
}

Expand Down Expand Up @@ -223,10 +224,13 @@ async fn local_event_loop(ctx: ConnectCtx) -> Result<(), IcmtError> {
surface.into_canvas()?
};

let mut view_events = vec![];
let mut update_events = vec![];
let ev0 = skip_event(&ctx);

let mut dump_events = vec![];
let mut view_events = vec![ev0.clone()];
let mut update_events = vec![ev0.clone()];
let mut dump_events = vec![ev0.clone()];

let mut dump_graphics = vec![];
let mut engiffen_paths = vec![];

let (update_in, update_out) = /* Begin update task */ {
Expand All @@ -235,13 +239,14 @@ async fn local_event_loop(ctx: ConnectCtx) -> Result<(), IcmtError> {
// Interaction cycle as two halves (local/remote); each half is a thread.
// There are four end points along the cycle's halves:
let (local_out, remote_in) = mpsc::channel::<ServiceCall>();
let (remote_out, local_in) = mpsc::channel::<()>();
let (remote_out, local_in) = mpsc::channel::<Vec<graphics::Result>>();

// 1. Remote interactions via update calls to service.
// (Consumes remote_in and produces remote_out).

task::spawn(do_update_task(cfg, remote_in, remote_out));
local_out.send(ServiceCall::Update(vec![skip_event(&ctx)]))?;
let req = if ctx.cfg.cli_opt.all_graphics { graphics::Request::All(window_dim.clone()) } else { graphics::Request::None };
local_out.send(ServiceCall::Update(vec![ev0.clone()], req))?;
(local_in, local_out)
};

Expand All @@ -251,13 +256,13 @@ async fn local_event_loop(ctx: ConnectCtx) -> Result<(), IcmtError> {
// Interaction cycle as two halves (local/remote); each half is a thread.
// There are four end points along the cycle's halves:
let (local_out, remote_in) = mpsc::channel::<Option<(graphics::Dim, Vec<event::EventInfo>)>>();
let (remote_out, local_in) = mpsc::channel::<Option<graphics::Result>>();
let (remote_out, local_in) = mpsc::channel::<graphics::Result>();

// 1. Remote interactions via view calls to service.
// (Consumes remote_in and produces remote_out).

task::spawn(do_view_task(cfg, remote_in, remote_out));
local_out.send(Some((window_dim.clone(), vec![skip_event(&ctx)])))?;
local_out.send(Some((window_dim.clone(), vec![ev0.clone()])))?;
(local_in, local_out)
};

Expand Down Expand Up @@ -327,10 +332,16 @@ async fn local_event_loop(ctx: ConnectCtx) -> Result<(), IcmtError> {
event::Event::WindowSize(new_dim) => {
info!("WindowSize {:?}", new_dim);
dirty_flag = true;
view_events.push(skip_event(&ctx));
dump_events.push(skip_event(&ctx));
write_gifs(&ctx.cfg.cli_opt, &window_dim, dump_events, &engiffen_paths)?;
dump_events = vec![];
let skip = skip_event(&ctx);
view_events.push(skip.clone());
dump_events.push(skip);
write_gifs(
&ctx.cfg.cli_opt,
&window_dim,
vec![],
&vec![],
&engiffen_paths,
)?;
engiffen_paths = vec![];
window_dim = new_dim;
// to do -- add event to buffer, and send to service
Expand Down Expand Up @@ -369,17 +380,26 @@ async fn local_event_loop(ctx: ConnectCtx) -> Result<(), IcmtError> {
/* attend to update task */
{
match update_in.try_recv() {
Ok(()) => {
Ok(graphics) => {
info!("graphics.len() = {}", graphics.len());
dump_graphics.extend(graphics);
update_responses += 1;
info!("update_responses = {}", update_responses);
let req = if ctx.cfg.cli_opt.all_graphics {
graphics::Request::All(window_dim.clone())
} else {
graphics::Request::None
};
update_out
.send(ServiceCall::Update(view_events.clone()))
.send(ServiceCall::Update(view_events.clone(), req))
.unwrap();
if quit_request {
println!("Continue: Quitting...");
println!("Waiting for final update-task response.");
match update_in.try_recv() {
Ok(()) => {
Ok(graphics) => {
info!("graphics.len() = {}", graphics.len());
dump_graphics.extend(graphics);
update_out.send(ServiceCall::FlushQuit)?;
println!("Done.");
}
Expand All @@ -393,22 +413,35 @@ async fn local_event_loop(ctx: ConnectCtx) -> Result<(), IcmtError> {
dirty_flag = true;
}
Err(mpsc::TryRecvError::Empty) => {
/* Update task not ready */
if quit_request {
println!("Continue: Quitting...");
println!("Waiting for final update-task response.");
update_in.recv()?;
let graphics = update_in.recv()?;
info!("graphics.len() = {}", graphics.len());
dump_graphics.extend(graphics);
update_out.send(ServiceCall::FlushQuit)?;
println!("Done.");
} else {
/* not ready; do nothing */
}
}
Err(e) => error!("{:?}", e),
Err(e) => {
error!("Update task error: {:?}", e);
println!("Cannot recover; quiting...");
quit_request = true;
}
}
};

if quit_request {
write_gifs(&ctx.cfg.cli_opt, &window_dim, dump_events, &engiffen_paths)?;
write_gifs(
&ctx.cfg.cli_opt,
&window_dim,
dump_events,
&dump_graphics,
&engiffen_paths,
)?;
{
print!("Stopping view task... ");
view_out.send(None)?;
Expand Down Expand Up @@ -462,9 +495,9 @@ async fn local_event_loop(ctx: ConnectCtx) -> Result<(), IcmtError> {
pub async fn service_call(
ctx: &ConnectCtx,
call: ServiceCall,
) -> IcmtResult<Option<graphics::Result>> {
) -> IcmtResult<Vec<graphics::Result>> {
if let ServiceCall::FlushQuit = call {
return Ok(None);
return Ok(vec![]);
};
debug!(
"service_call: to canister_id {:?} at replica_url {:?}",
Expand All @@ -475,11 +508,10 @@ pub async fn service_call(
.timeout(REQUEST_TIMEOUT)
.build();
let timestamp = std::time::SystemTime::now();
info!("service_call: {:?}", call);
let arg_bytes = match call.clone() {
ServiceCall::FlushQuit => candid::encode_args(()).unwrap(),
ServiceCall::View(window_dim, evs) => candid::encode_args((window_dim, evs)).unwrap(),
ServiceCall::Update(evs) => candid::encode_args((evs,)).unwrap(),
ServiceCall::Update(evs, req) => candid::encode_args((evs, req)).unwrap(),
};
info!(
"service_call: Encoded argument via Candid; Arg size {:?} bytes",
Expand All @@ -498,7 +530,7 @@ pub async fn service_call(
.await?;
Some(resp)
}
ServiceCall::Update(_keys) => {
ServiceCall::Update(_keys, _gfx_req) => {
let resp = ctx
.agent
.update(&ctx.canister_id, "update")
Expand All @@ -516,8 +548,15 @@ pub async fn service_call(
elapsed
);
match call.clone() {
ServiceCall::FlushQuit => Ok(None),
ServiceCall::Update(_) => Ok(None),
ServiceCall::FlushQuit => Ok(vec![]),
ServiceCall::Update(_, _) => match candid::Decode!(&(*blob_res), Vec<graphics::Result>)
{
Ok(res) => Ok(res),
Err(candid_err) => {
error!("Candid decoding error: {:?}", candid_err);
Err(IcmtError::String("decoding error".to_string()))
}
},
ServiceCall::View(_, _) => match candid::Decode!(&(*blob_res), graphics::Result) {
Ok(res) => {
if ctx.cfg.cli_opt.log_trace {
Expand All @@ -531,7 +570,7 @@ pub async fn service_call(
res_log
);
}
Ok(Some(res))
Ok(vec![res])
}
Err(candid_err) => {
error!("Candid decoding error: {:?}", candid_err);
Expand All @@ -540,8 +579,10 @@ pub async fn service_call(
},
}
} else {
let res = format!("{:?}", blob_res);
info!("..error result {:?}", res);
error!(
"service_call: Error result: {:?}; elapsed time {:?}",
blob_res, elapsed
);
Err(IcmtError::String("ic-mt error".to_string()))
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/lib/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ pub struct CliOpt {
/// Suppress window for graphics output.
#[structopt(short = "W", long = "no-window")]
pub no_window: bool,
/// Suppress capturing graphics output.
/// Suppress capturing video and graphics output.
#[structopt(short = "C", long = "no-capture")]
pub no_capture: bool,
/// Dump all graphics for updates; for generating replay tests.
#[structopt(short = "G", long = "all-graphics")]
pub all_graphics: bool,
/// Trace-level logging (most verbose)
#[structopt(short = "t", long = "trace-log")]
pub log_trace: bool,
Expand Down
8 changes: 4 additions & 4 deletions src/lib/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,25 +104,25 @@ pub fn draw_elm<T: RenderTarget>(
pub async fn draw<T: RenderTarget>(
canvas: &mut Canvas<T>,
dim: &graphics::Dim,
rr: &Option<graphics::Result>,
rr: &graphics::Result,
) -> Result<(), String> {
let pos = graphics::Pos {
x: nat_zero(),
y: nat_zero(),
};
let fill = graphics::Fill::Closed((nat_zero(), nat_zero(), nat_zero()));
match rr {
Some(graphics::Result::Ok(graphics::Out::Draw(elm))) => {
graphics::Result::Ok(graphics::Out::Draw(elm)) => {
draw_rect_elms(canvas, &pos, dim, &fill, &vec![elm.clone()])?;
}
Some(graphics::Result::Ok(graphics::Out::Redraw(elms))) => {
graphics::Result::Ok(graphics::Out::Redraw(elms)) => {
if elms.len() == 1 && elms[0].0 == "screen" {
draw_rect_elms(canvas, &pos, dim, &fill, &vec![elms[0].1.clone()])?;
} else {
warn!("unrecognized redraw elements {:?}", elms);
}
}
Some(graphics::Result::Err(graphics::Out::Draw(elm))) => {
graphics::Result::Err(graphics::Out::Draw(elm)) => {
draw_rect_elms(canvas, &pos, dim, &fill, &vec![elm.clone()])?;
}
_ => unimplemented!(),
Expand Down
Loading

0 comments on commit c26ac37

Please sign in to comment.