1253 lines
43 KiB
Rust
1253 lines
43 KiB
Rust
use super::*;
|
|
use crate::document::Document;
|
|
use oa_core::{
|
|
language,
|
|
source_project::{ProjectLimits, SourceBundle},
|
|
validation,
|
|
};
|
|
|
|
fn id(p: &Project, alias: &str) -> Id {
|
|
p.identities()
|
|
.into_iter()
|
|
.find(|(_, name)| *name == alias)
|
|
.unwrap()
|
|
.0
|
|
}
|
|
|
|
fn fixture(lifetime: bool) -> (Document, Id) {
|
|
let (name, input, owner) = if lifetime {
|
|
("lifetime.arcadia", LIFETIME, "lifetime_conversation")
|
|
} else {
|
|
("fixture.arcadia", FIXTURE, "conversation")
|
|
};
|
|
let p = language::parse(input).expect("existing real Arcadia fixture");
|
|
let errors: Vec<_> = validation::validate(&p)
|
|
.into_iter()
|
|
.filter(|issue| issue.severity == validation::Severity::Error)
|
|
.collect();
|
|
assert!(errors.is_empty(), "{errors:?}");
|
|
let bundle = SourceBundle {
|
|
entry: name.into(),
|
|
files: BTreeMap::from([(name.into(), input.into())]),
|
|
};
|
|
let document = Document::from_bundle(bundle, ProjectLimits::default()).unwrap();
|
|
assert_eq!(document.project(), &p);
|
|
assert_eq!(language::parse(&language::format(&p)).unwrap(), p);
|
|
let owner = id(&p, owner);
|
|
(document, owner)
|
|
}
|
|
|
|
fn passive(document: &Document, before: &Project, source: &SourceBundle) {
|
|
assert_eq!(document.project(), before);
|
|
assert_eq!(document.bundle(), source);
|
|
assert!(!document.dirty());
|
|
assert!(!document.can_undo() && !document.can_redo());
|
|
}
|
|
|
|
fn row(scene: &Scene, id: Id) -> f32 {
|
|
scene.rows[&id].1
|
|
}
|
|
fn line(scene: &Scene, id: Id) -> &Line {
|
|
assert_eq!(scene.lines.iter().filter(|line| line.id == id).count(), 1);
|
|
scene.lines.iter().find(|line| line.id == id).unwrap()
|
|
}
|
|
fn named_box(scene: &Scene, id: Id) -> &BoxItem {
|
|
scene
|
|
.boxes
|
|
.iter()
|
|
.find(|item| item.id == id && !item.title.is_empty())
|
|
.unwrap()
|
|
}
|
|
|
|
fn same_scene(a: &Scene, b: &Scene) {
|
|
assert!(
|
|
!a.activations.is_empty(),
|
|
"the complete typed comparison must be exercised"
|
|
);
|
|
assert_eq!(a.activations, b.activations);
|
|
assert_eq!(a.rows, b.rows);
|
|
assert_eq!(a.sequence_bounds, b.sequence_bounds);
|
|
assert_eq!(a.size, b.size);
|
|
assert_eq!(a.layout_origin, b.layout_origin);
|
|
assert_eq!(a.lanes, b.lanes);
|
|
assert_eq!(a.render_lanes, b.render_lanes);
|
|
assert_eq!(a.lifecycle, b.lifecycle);
|
|
assert_eq!(a.open_calls, b.open_calls);
|
|
let boxes = |s: &Scene| {
|
|
s.boxes
|
|
.iter()
|
|
.map(|b| (b.id, b.rect, b.title.clone(), b.detail.clone(), b.marker))
|
|
.collect::<Vec<_>>()
|
|
};
|
|
let lines = |s: &Scene| {
|
|
s.lines
|
|
.iter()
|
|
.map(|l| {
|
|
(
|
|
l.id,
|
|
l.points.clone(),
|
|
l.label_at,
|
|
l.label.clone(),
|
|
l.dashed,
|
|
)
|
|
})
|
|
.collect::<Vec<_>>()
|
|
};
|
|
let slots = |s: &Scene| {
|
|
s.slots
|
|
.iter()
|
|
.map(|slot| {
|
|
(
|
|
slot.slot.parent,
|
|
slot.slot.before,
|
|
slot.y,
|
|
slot.label.clone(),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>()
|
|
};
|
|
assert_eq!(boxes(a), boxes(b));
|
|
assert_eq!(lines(a), lines(b));
|
|
assert_eq!(slots(a), slots(b));
|
|
}
|
|
|
|
const FIXTURE: &str = r###"arcadia 7;
|
|
project layout_probe "Behavior layout Scene probe" id "10000000-0000-4000-8000-000000000001" {
|
|
oa_entity sender "Sender" id "10000000-0000-4000-8000-000000000002" {}
|
|
oa_entity receiver "Receiver" id "10000000-0000-4000-8000-000000000003" {}
|
|
oa_activity inspect "Inspect" id "10000000-0000-4000-8000-000000000004" {}
|
|
allocation inspect_assignment "Receiver performs inspection" id "10000000-0000-4000-8000-000000000005" {
|
|
source @receiver; target @inspect;
|
|
}
|
|
communication_mean channel "Shared channel" id "10000000-0000-4000-8000-000000000006" {
|
|
source @sender; target @receiver; oriented false;
|
|
}
|
|
oa_scenario conversation "Conversation" id "10000000-0000-4000-8000-000000000007" {
|
|
viewpoint ParticipantInteraction;
|
|
steps [@first, @second, @fragment, @self_call, @self_reply];
|
|
}
|
|
oa_lifeline left "Sender role" id "10000000-0000-4000-8000-000000000008" { parent @conversation; represented @sender; }
|
|
oa_lifeline right "Receiver role" id "10000000-0000-4000-8000-000000000009" { parent @conversation; represented @receiver; }
|
|
oa_occurrence first "First delivery" id "10000000-0000-4000-8000-00000000000a" {
|
|
parent @conversation;
|
|
body { Exchange {
|
|
exchange @channel; sender @left; receiver @right;
|
|
message_kind Asynchronous;
|
|
}; };
|
|
}
|
|
oa_occurrence second "Second delivery" id "10000000-0000-4000-8000-00000000000b" {
|
|
parent @conversation;
|
|
body { Exchange {
|
|
exchange @channel; sender @left; receiver @right;
|
|
message_kind Asynchronous;
|
|
}; };
|
|
}
|
|
oa_occurrence fragment "Optional inspection" id "10000000-0000-4000-8000-00000000000c" {
|
|
parent @conversation;
|
|
body { Fragment { operator Optional; branches [@operand]; }; };
|
|
}
|
|
oa_occurrence operand "When requested" id "10000000-0000-4000-8000-00000000000d" {
|
|
parent @fragment;
|
|
body { Branch { guard "requested"; occurrences [@nested]; }; };
|
|
}
|
|
oa_occurrence nested "Inspection occurrence" id "10000000-0000-4000-8000-00000000000e" {
|
|
parent @operand;
|
|
body { Action { action @inspect; lifeline @right; }; };
|
|
}
|
|
// Self calls intentionally have no global self-edge: such a relation is
|
|
// invalid in the current metamodel. Their unassigned exchange is a known
|
|
// incomplete-model warning, while explicit lifelines make Scene drawable.
|
|
oa_occurrence self_call "Local call" id "10000000-0000-4000-8000-00000000000f" {
|
|
parent @conversation;
|
|
body { Exchange {
|
|
exchange none; sender @right; receiver @right; message_kind Call;
|
|
}; };
|
|
}
|
|
oa_occurrence self_reply "Local reply" id "10000000-0000-4000-8000-000000000010" {
|
|
parent @conversation;
|
|
body { Exchange {
|
|
exchange none; sender @right; receiver @right;
|
|
message_kind Reply; reply_to @self_call;
|
|
}; };
|
|
}
|
|
}
|
|
"###;
|
|
|
|
const LIFETIME: &str = r###"arcadia 7;
|
|
// Separate lifetime probe: preserve this authored note and all stable IDs.
|
|
project lifetime_probe "Delayed receiver lifetime" id "20000000-0000-4000-8000-000000000001" {
|
|
oa_activity prepare "Prepare request" id "20000000-0000-4000-8000-000000000002" {}
|
|
oa_activity serve "Serve request" id "20000000-0000-4000-8000-000000000003" {}
|
|
interaction service "Service request" id "20000000-0000-4000-8000-000000000004" {
|
|
source @prepare; target @serve;
|
|
}
|
|
oa_scenario lifetime_conversation "Receiver created on demand" id "20000000-0000-4000-8000-000000000005" {
|
|
viewpoint OperationalActivity;
|
|
steps [@before_birth, @create_receiver, @service_call, @delete_receiver];
|
|
}
|
|
oa_lifeline requester "Requester" id "20000000-0000-4000-8000-000000000006" {
|
|
parent @lifetime_conversation; represented @prepare;
|
|
}
|
|
oa_lifeline provider "Provider" id "20000000-0000-4000-8000-000000000007" {
|
|
parent @lifetime_conversation; represented @serve;
|
|
}
|
|
oa_occurrence before_birth "Prepare before receiver exists" id "20000000-0000-4000-8000-000000000008" {
|
|
parent @lifetime_conversation;
|
|
body { Action { action @prepare; lifeline @requester; }; };
|
|
}
|
|
oa_occurrence create_receiver "Create receiver" id "20000000-0000-4000-8000-000000000009" {
|
|
parent @lifetime_conversation;
|
|
body { Exchange {
|
|
exchange @service; sender @requester; receiver @provider;
|
|
message_kind Create;
|
|
}; };
|
|
}
|
|
oa_occurrence service_call "Call receiver" id "20000000-0000-4000-8000-00000000000a" {
|
|
parent @lifetime_conversation;
|
|
body { Exchange {
|
|
exchange @service; sender @requester; receiver @provider;
|
|
message_kind Call;
|
|
}; };
|
|
}
|
|
// Death ends the visible activation but deliberately does not invent a Reply.
|
|
oa_occurrence delete_receiver "Delete receiver" id "20000000-0000-4000-8000-00000000000b" {
|
|
parent @lifetime_conversation;
|
|
body { Exchange {
|
|
exchange @service; sender @requester; receiver @provider;
|
|
message_kind Delete;
|
|
}; };
|
|
}
|
|
}
|
|
"###;
|
|
|
|
fn saved(p: &mut Project, owner: Id, layout: Option<ScenarioLayout>) -> Id {
|
|
let view = Id::new_v4();
|
|
p.diagrams.insert(
|
|
view,
|
|
oa_core::Diagram {
|
|
id: view,
|
|
alias: format!("view_{}", p.diagrams.len()),
|
|
name: "Saved conversation".into(),
|
|
description: String::new(),
|
|
layer: p.elements[&owner].layer(),
|
|
projection: oa_core::ProjectionKind::Scenario { scenario: owner },
|
|
view_options: Default::default(),
|
|
nodes: BTreeMap::new(),
|
|
routes: vec![],
|
|
layout: layout.map(DiagramLayout::Scenario),
|
|
},
|
|
);
|
|
view
|
|
}
|
|
fn positions(messages: &[(Id, f32)], lanes: &[(Id, f32)]) -> ScenarioLayout {
|
|
ScenarioLayout {
|
|
lanes: lanes
|
|
.iter()
|
|
.map(|(id, x)| oa_core::ScenarioLanePosition {
|
|
lifeline: *id,
|
|
x: *x,
|
|
})
|
|
.collect(),
|
|
messages: messages
|
|
.iter()
|
|
.map(|(id, y)| oa_core::ScenarioMessagePosition {
|
|
address: oa_core::OccurrenceEndpoint {
|
|
references: vec![],
|
|
occurrence: *id,
|
|
},
|
|
send_y: *y,
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
fn stored(p: Project) -> Document {
|
|
let errors: Vec<_> = validation::validate(&p)
|
|
.into_iter()
|
|
.filter(|issue| issue.severity == validation::Severity::Error)
|
|
.collect();
|
|
assert!(errors.is_empty(), "{errors:?}");
|
|
let text = format!(
|
|
"// Preserve source and identities through passive resolution.\n{}",
|
|
language::format(&p)
|
|
);
|
|
let document = Document::from_bundle(
|
|
SourceBundle {
|
|
entry: "saved.arcadia".into(),
|
|
files: BTreeMap::from([("saved.arcadia".into(), text)]),
|
|
},
|
|
ProjectLimits::default(),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(document.project(), &p);
|
|
document
|
|
}
|
|
fn result(p: &Project, view: Id) -> ResolvedScenarioPlan {
|
|
resolve(p, Representation::Saved { diagram: view }, view)
|
|
.unwrap_or_else(|issues| panic!("{issues:?}"))
|
|
}
|
|
fn failures(p: &Project, view: Id) -> Vec<GeometryIssue> {
|
|
match resolve(p, Representation::Saved { diagram: view }, view) {
|
|
Ok(_) => panic!("expected explicit unsupported geometry"),
|
|
Err(issues) => issues,
|
|
}
|
|
}
|
|
fn contains(outer: Rectangle, inner: Rectangle) -> bool {
|
|
outer.x <= inner.x
|
|
&& outer.y <= inner.y
|
|
&& outer.x + outer.width >= inner.x + inner.width
|
|
&& outer.y + outer.height >= inner.y + inner.height
|
|
}
|
|
|
|
#[test]
|
|
fn absent_empty_and_explicit_baseline_saved_values_preserve_complete_scene_and_source() {
|
|
for lifetime in [false, true] {
|
|
let (document, owner) = fixture(lifetime);
|
|
let mut p = document.project().clone();
|
|
let baseline = Scene::sequence(&p, owner, owner);
|
|
let messages: Vec<_> = baseline
|
|
.lines
|
|
.iter()
|
|
.map(|line| (line.id, line.points[0].y))
|
|
.collect();
|
|
let lane_values: Vec<_> = baseline.lanes.iter().map(|(id, x)| (*id, *x)).collect();
|
|
let views = [
|
|
saved(&mut p, owner, None),
|
|
saved(&mut p, owner, Some(ScenarioLayout::default())),
|
|
saved(&mut p, owner, Some(positions(&messages, &lane_values))),
|
|
];
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
for view in views {
|
|
let resolved = result(p, view);
|
|
assert_eq!(
|
|
resolved.representation,
|
|
Representation::Saved { diagram: view }
|
|
);
|
|
assert_eq!(resolved.owner, owner);
|
|
same_scene(&baseline, &resolved.scene);
|
|
}
|
|
let derived = resolve(p, Representation::Derived { owner }, owner)
|
|
.unwrap_or_else(|e| panic!("{e:?}"));
|
|
same_scene(&baseline, &derived.scene);
|
|
passive(&document, &before, &source);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn independent_saved_appearances_of_reused_exchange_have_exact_anchors_and_no_ambient_state() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let first = id(&p, "first");
|
|
let second = id(&p, "second");
|
|
let a = saved(&mut p, owner, Some(positions(&[(second, 200.)], &[])));
|
|
let b = saved(&mut p, owner, Some(positions(&[(first, 1000.)], &[])));
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let a1 = result(p, a);
|
|
let b1 = result(p, b);
|
|
let a2 = result(p, a);
|
|
assert_eq!(
|
|
(row(&a1.scene, first), row(&a1.scene, second)),
|
|
(148., 200.)
|
|
);
|
|
assert_eq!(
|
|
(row(&b1.scene, first), row(&b1.scene, second)),
|
|
(1000., 240.)
|
|
);
|
|
assert_eq!(
|
|
line(&a1.scene, second)
|
|
.points
|
|
.iter()
|
|
.map(|p| p.y)
|
|
.collect::<Vec<_>>(),
|
|
vec![200., 200.]
|
|
);
|
|
assert!(
|
|
b1.plan
|
|
.diagnostics
|
|
.contains(&Diagnostic::SourceOrderCrossing {
|
|
scope: owner,
|
|
before: first,
|
|
after: second
|
|
})
|
|
);
|
|
assert!(b1.bounds.y + b1.bounds.height >= 1008.);
|
|
same_scene(&a1.scene, &a2.scene);
|
|
same_scene(
|
|
&Scene::sequence(p, owner, owner),
|
|
&resolve(p, Representation::Derived { owner }, owner)
|
|
.unwrap_or_else(|e| panic!("{e:?}"))
|
|
.scene,
|
|
);
|
|
passive(&document, &before, &source);
|
|
}
|
|
|
|
#[test]
|
|
fn authored_lanes_precede_headers_arrows_lifetimes_and_complete_bounds() {
|
|
let (document, owner) = fixture(true);
|
|
let mut p = document.project().clone();
|
|
let requester = id(&p, "requester");
|
|
let provider = id(&p, "provider");
|
|
let create = id(&p, "create_receiver");
|
|
let call = id(&p, "service_call");
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[], &[(requester, 900.), (provider, -500.)])),
|
|
);
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let resolved = result(p, view);
|
|
let scene = &resolved.scene;
|
|
assert_eq!(named_box(scene, provider).rect.x, -602.);
|
|
assert_eq!(
|
|
line(scene, create).points,
|
|
vec![Point::new(900., 243.), Point::new(-430., 243.)]
|
|
);
|
|
assert_eq!(
|
|
line(scene, call).points,
|
|
vec![Point::new(900., 335.), Point::new(-494., 335.)]
|
|
);
|
|
assert_eq!(
|
|
scene.lifecycle.marker_at(Point::new(-500., 243.)),
|
|
Some(create)
|
|
);
|
|
assert_eq!(scene.activations[0].rect.x, -506.);
|
|
for item in &scene.boxes {
|
|
assert!(contains(resolved.bounds, item.rect));
|
|
}
|
|
for bar in &scene.activations {
|
|
assert!(contains(resolved.bounds, bar.rect));
|
|
}
|
|
assert_eq!(resolved.scene.layout_origin.x, -660.);
|
|
assert_eq!(resolved.bounds.x, -660.);
|
|
assert_eq!(resolved.bounds.x + resolved.bounds.width, 1140.);
|
|
passive(&document, &before, &source);
|
|
}
|
|
|
|
#[test]
|
|
fn same_x_distinct_lifelines_are_not_self_messages_in_either_bridge() {
|
|
let (document, owner) = fixture(true);
|
|
let mut p = document.project().clone();
|
|
let requester = id(&p, "requester");
|
|
let provider = id(&p, "provider");
|
|
let lanes = BTreeMap::from([(requester, 160.), (provider, 160.)]);
|
|
let baseline = Scene::sequence_geometry(&p, owner, owner, Some(&lanes), None);
|
|
assert_eq!(line(&baseline, id(&p, "create_receiver")).points.len(), 2);
|
|
assert_eq!(line(&baseline, id(&p, "create_receiver")).points[0].y, 243.);
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[], &[(requester, 160.), (provider, 160.)])),
|
|
);
|
|
assert!(
|
|
failures(&p, view)
|
|
.iter()
|
|
.any(|e| matches!(e, GeometryIssue::CoincidentDistinctEndpoints(_)))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn self_call_receive_and_reply_send_drive_the_full_typed_execution() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let call = id(&p, "self_call");
|
|
let reply = id(&p, "self_reply");
|
|
let receiver = id(&p, "right");
|
|
let view = saved(&mut p, owner, Some(positions(&[(call, 610.)], &[])));
|
|
let resolved = result(&p, view);
|
|
let x = resolved.lanes[&receiver];
|
|
assert_eq!(
|
|
line(&resolved.scene, call).points,
|
|
vec![
|
|
Point::new(x, 610.),
|
|
Point::new(x + 66., 610.),
|
|
Point::new(x + 66., 634.),
|
|
Point::new(x + 6., 634.)
|
|
]
|
|
);
|
|
assert_eq!(
|
|
resolved.scene.activations,
|
|
vec![scenario_activation::Segment {
|
|
call,
|
|
receiver,
|
|
scope: owner,
|
|
rect: Rectangle::new(Point::new(x - 6., 634.), Size::new(12., 94.)),
|
|
column: 0,
|
|
finish: scenario_activation::Finish::Reply(reply),
|
|
}]
|
|
);
|
|
assert_eq!(line(&resolved.scene, reply).points[0].y, 728.);
|
|
assert_eq!(
|
|
resolved.plan.messages[&call],
|
|
MessageAnchors {
|
|
send_y: 610.,
|
|
receive_y: 634.
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn moved_death_clips_actual_bar_and_preserves_unanswered_cue() {
|
|
let (document, owner) = fixture(true);
|
|
let mut p = document.project().clone();
|
|
let call = id(&p, "service_call");
|
|
let delete = id(&p, "delete_receiver");
|
|
let provider = id(&p, "provider");
|
|
let view = saved(&mut p, owner, Some(positions(&[(delete, 380.)], &[])));
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let resolved = result(p, view);
|
|
let x = resolved.lanes[&provider];
|
|
assert_eq!(
|
|
resolved.scene.activations,
|
|
vec![scenario_activation::Segment {
|
|
call,
|
|
receiver: provider,
|
|
scope: owner,
|
|
rect: Rectangle::new(Point::new(x - 6., 335.), Size::new(12., 45.)),
|
|
column: 0,
|
|
finish: scenario_activation::Finish::Terminated(delete),
|
|
}]
|
|
);
|
|
assert_eq!(resolved.scene.open_calls, vec![Point::new(x + 10., 373.)]);
|
|
assert_eq!(
|
|
resolved
|
|
.scene
|
|
.lifecycle
|
|
.alive_spans(provider, owner, 0., 1000.)
|
|
.iter()
|
|
.map(|s| (s.start, s.end))
|
|
.collect::<Vec<_>>(),
|
|
vec![(243., 380.)]
|
|
);
|
|
assert_eq!(
|
|
resolved.scene.lifecycle.marker_at(Point::new(x, 380.)),
|
|
Some(delete)
|
|
);
|
|
passive(&document, &before, &source);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_root_scope_failures_do_not_clamp_calls_or_lifecycle_but_async_can_be_negative() {
|
|
let (document, owner) = fixture(true);
|
|
let mut p = document.project().clone();
|
|
let create = id(&p, "create_receiver");
|
|
let call = id(&p, "service_call");
|
|
for y in [50., 84.] {
|
|
let view = saved(&mut p, owner, Some(positions(&[(create, y)], &[])));
|
|
assert_eq!(
|
|
failures(&p, view),
|
|
vec![GeometryIssue::LifecycleAnchorOutsideRenderScope {
|
|
occurrence: create,
|
|
scope: owner
|
|
}]
|
|
);
|
|
}
|
|
let view = saved(&mut p, owner, Some(positions(&[(create, 85.)], &[])));
|
|
assert_eq!(result(&p, view).scene.rows[&create].1, 85.);
|
|
let view = saved(&mut p, owner, Some(positions(&[(call, -10.)], &[])));
|
|
assert_eq!(
|
|
failures(&p, view),
|
|
vec![GeometryIssue::TypedActivationOutsideScope { call, scope: owner }]
|
|
);
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let first = id(&p, "first");
|
|
let view = saved(&mut p, owner, Some(positions(&[(first, -10.)], &[])));
|
|
let resolved = result(&p, view);
|
|
assert_eq!(line(&resolved.scene, first).points[0].y, -10.);
|
|
assert!(resolved.bounds.y <= -36.);
|
|
}
|
|
|
|
#[test]
|
|
fn reply_crossing_is_a_specific_typed_failure() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let call = id(&p, "self_call");
|
|
let reply = id(&p, "self_reply");
|
|
let view = saved(&mut p, owner, Some(positions(&[(reply, 600.)], &[])));
|
|
assert_eq!(
|
|
failures(&p, view),
|
|
vec![GeometryIssue::TypedActivationCrossingNotSupported { call, event: reply }]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn complete_scope_plan_rejects_escape_preserves_overlap_and_keeps_headers_and_followers() {
|
|
// Internal complete-plan oracle: observations are automatic in the persisted
|
|
// first slice, but its dependent scope math must retain the proven boundary.
|
|
let (document, owner) = fixture(false);
|
|
let p = document.project();
|
|
let baseline = Scene::sequence(p, owner, owner);
|
|
let nested = id(p, "nested");
|
|
let frame = id(p, "fragment");
|
|
let operand = id(p, "operand");
|
|
assert_eq!(
|
|
Plan::build(p, owner, &baseline, &Candidate::from([(nested, 353.)])).unwrap_err(),
|
|
GeometryIssue::OperandScopeOutsideEnclosingInterval {
|
|
operand,
|
|
fragment: frame
|
|
}
|
|
);
|
|
let plan = Plan::build(p, owner, &baseline, &Candidate::from([(nested, 380.)])).unwrap();
|
|
let scene = Scene::sequence_geometry(p, owner, owner, None, Some(&plan));
|
|
assert_eq!(named_box(&scene, nested).rect.y, 360.);
|
|
assert_eq!(scene.sequence_bounds[&operand], (338., 530.));
|
|
assert_eq!(
|
|
named_box(&scene, frame).rect,
|
|
named_box(&baseline, frame).rect
|
|
);
|
|
assert_eq!(
|
|
scene
|
|
.slots
|
|
.iter()
|
|
.find(|slot| slot.slot.before == Some(frame))
|
|
.unwrap()
|
|
.y,
|
|
290.
|
|
);
|
|
assert!(!scene.activations.is_empty());
|
|
assert_eq!(scene.activations, baseline.activations);
|
|
for (id, row) in &baseline.rows {
|
|
if *id != nested {
|
|
assert_eq!(scene.rows[id], *row);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unfinished_coordinate_is_latent_and_zero_or_signed_zero_never_rewrites_source() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let second = id(&p, "second");
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { receiver, .. },
|
|
..
|
|
}) = p.elements.get_mut(&second).unwrap().shared_mut()
|
|
{
|
|
*receiver = None;
|
|
}
|
|
let left = id(&p, "left");
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[(second, -0.)], &[(left, -0.)])),
|
|
);
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let resolved = result(p, view);
|
|
assert_eq!(resolved.latent_messages, vec![second]);
|
|
assert!(!resolved.scene.lines.iter().any(|line| line.id == second));
|
|
assert_eq!(resolved.scene.rows[&second].1, 240.);
|
|
assert_eq!(named_box(&resolved.scene, second).rect.y, 220.);
|
|
passive(&document, &before, &source);
|
|
}
|
|
|
|
#[test]
|
|
fn self_lifecycle_saved_send_is_converted_once_to_the_actual_receive_marker() {
|
|
let (document, owner) = fixture(true);
|
|
let mut p = document.project().clone();
|
|
let create = id(&p, "create_receiver");
|
|
let delete = id(&p, "delete_receiver");
|
|
let provider = id(&p, "provider");
|
|
for event in [create, delete] {
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange {
|
|
sender, exchange, ..
|
|
},
|
|
..
|
|
}) = p.elements.get_mut(&event).unwrap().shared_mut()
|
|
{
|
|
*sender = Some(provider);
|
|
*exchange = None;
|
|
}
|
|
}
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[(create, 200.), (delete, 400.)], &[])),
|
|
);
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let resolved = result(p, view);
|
|
let x = resolved.lanes[&provider];
|
|
for (event, send, receive) in [(create, 200., 224.), (delete, 400., 424.)] {
|
|
let points = &line(&resolved.scene, event).points;
|
|
assert_eq!(points.len(), 4);
|
|
assert_eq!(points[0].y, send);
|
|
assert_eq!(points[3].y, receive);
|
|
assert_eq!(resolved.scene.rows[&event].1, receive);
|
|
assert_eq!(
|
|
resolved.plan.messages[&event],
|
|
MessageAnchors {
|
|
send_y: send,
|
|
receive_y: receive
|
|
}
|
|
);
|
|
assert_eq!(
|
|
resolved.scene.lifecycle.marker_at(Point::new(x, receive)),
|
|
Some(event)
|
|
);
|
|
}
|
|
assert_eq!(
|
|
resolved
|
|
.scene
|
|
.lifecycle
|
|
.alive_spans(provider, owner, 0., 1000.)
|
|
.iter()
|
|
.map(|s| (s.start, s.end))
|
|
.collect::<Vec<_>>(),
|
|
vec![(224., 424.)]
|
|
);
|
|
assert_eq!(resolved.scene.activations.len(), 1);
|
|
assert_eq!(
|
|
resolved.scene.activations[0].finish,
|
|
scenario_activation::Finish::Terminated(delete)
|
|
);
|
|
passive(&document, &before, &source);
|
|
}
|
|
|
|
#[test]
|
|
fn timer_uses_implicit_effective_receiver_and_true_self_offset() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let first = id(&p, "first");
|
|
if let Some(SharedBody::Occurrence {
|
|
body:
|
|
OccurrenceBody::Exchange {
|
|
receiver,
|
|
exchange,
|
|
message_kind,
|
|
..
|
|
},
|
|
..
|
|
}) = p.elements.get_mut(&first).unwrap().shared_mut()
|
|
{
|
|
*receiver = None;
|
|
*exchange = None;
|
|
*message_kind = MessageKind::Timer {
|
|
expression: "after 10 ms".into(),
|
|
};
|
|
}
|
|
let view = saved(&mut p, owner, Some(positions(&[(first, 180.)], &[])));
|
|
let document = stored(p);
|
|
let resolved = result(document.project(), view);
|
|
let points = &line(&resolved.scene, first).points;
|
|
assert_eq!(points.len(), 4);
|
|
assert_eq!((points[0].y, points[3].y), (180., 204.));
|
|
assert_eq!(points[0].x, points[3].x);
|
|
assert_eq!(
|
|
resolved.plan.messages[&first],
|
|
MessageAnchors {
|
|
send_y: 180.,
|
|
receive_y: 204.
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn latent_message_becomes_exact_only_when_its_own_endpoint_is_completed() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let first = id(&p, "first");
|
|
let second = id(&p, "second");
|
|
let receiver = id(&p, "right");
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { receiver, .. },
|
|
..
|
|
}) = p.elements.get_mut(&second).unwrap().shared_mut()
|
|
{
|
|
*receiver = None;
|
|
}
|
|
let view = saved(&mut p, owner, Some(positions(&[(second, 200.)], &[])));
|
|
let pending = result(&p, view);
|
|
assert_eq!(pending.latent_messages, vec![second]);
|
|
let authored = p.diagrams[&view].layout.clone();
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange {
|
|
receiver: target, ..
|
|
},
|
|
..
|
|
}) = p.elements.get_mut(&second).unwrap().shared_mut()
|
|
{
|
|
*target = Some(receiver);
|
|
}
|
|
let document = stored(p);
|
|
let complete = result(document.project(), view);
|
|
assert!(complete.latent_messages.is_empty());
|
|
assert_eq!(
|
|
line(&complete.scene, second)
|
|
.points
|
|
.iter()
|
|
.map(|point| point.y)
|
|
.collect::<Vec<_>>(),
|
|
vec![200., 200.]
|
|
);
|
|
assert_eq!(line(&complete.scene, first).points[0].y, 148.);
|
|
assert_eq!(document.project().diagrams[&view].layout, authored);
|
|
}
|
|
|
|
#[test]
|
|
fn wide_gap_bounds_include_all_actual_shapes_and_typed_extensions() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let left = id(&p, "left");
|
|
let right = id(&p, "right");
|
|
let call = id(&p, "self_call");
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[], &[(left, -1_000_000.), (right, 1_000_000.)])),
|
|
);
|
|
let resolved = result(&p, view);
|
|
assert_eq!(resolved.bounds.x, -1_000_160.);
|
|
assert_eq!(resolved.bounds.x + resolved.bounds.width, 1_000_240.);
|
|
let frame = named_box(&resolved.scene, id(&p, "fragment")).rect;
|
|
assert!(frame.x < resolved.lanes[&left] && frame.x + frame.width > resolved.lanes[&right]);
|
|
assert_eq!(line(&resolved.scene, call).points[1].x, 1_000_066.);
|
|
assert!(!resolved.scene.activations.is_empty());
|
|
for segment in &resolved.scene.activations {
|
|
assert!(contains(resolved.bounds, segment.rect));
|
|
}
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
// The same owner also admits a separate representation with one shifted lane.
|
|
let right = id(&p, "right");
|
|
let view = saved(&mut p, owner, Some(positions(&[], &[(right, 900.)])));
|
|
let resolved = result(&p, view);
|
|
assert!(
|
|
resolved.bounds.x + resolved.bounds.width
|
|
>= named_box(&resolved.scene, right).rect.x + 204.
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn earlier_receiver_death_and_prebirth_call_are_named_typed_failures_not_disappearing_bars() {
|
|
let (document, owner) = fixture(true);
|
|
let mut p = document.project().clone();
|
|
let call = id(&p, "service_call");
|
|
let delete = id(&p, "delete_receiver");
|
|
let provider = id(&p, "provider");
|
|
let view = saved(&mut p, owner, Some(positions(&[(delete, 300.)], &[])));
|
|
assert_eq!(
|
|
failures(&p, view),
|
|
vec![GeometryIssue::TypedActivationCrossingNotSupported {
|
|
call,
|
|
event: delete
|
|
}]
|
|
);
|
|
let view = saved(&mut p, owner, Some(positions(&[(call, 200.)], &[])));
|
|
assert_eq!(
|
|
failures(&p, view),
|
|
vec![GeometryIssue::TypedActivationOutsideLifetime {
|
|
call,
|
|
receiver: provider
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unsupported_expansion_duplicate_keys_and_foreign_identity_fail_without_mutating_input() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let first = id(&p, "first");
|
|
let mut layout = positions(&[(first, 200.), (first, 300.)], &[]);
|
|
layout.messages[0].address.references.push(owner);
|
|
let view = saved(&mut p, owner, Some(layout));
|
|
let before = p.clone();
|
|
let issues = failures(&p, view);
|
|
assert!(issues.contains(&GeometryIssue::ExpandedAddressNotSupported(first)));
|
|
assert!(issues.contains(&GeometryIssue::DuplicateCoordinate(first)));
|
|
assert_eq!(p, before);
|
|
assert_eq!(
|
|
failures(&p, Id::nil()),
|
|
vec![GeometryIssue::InvalidRepresentation(Id::nil())]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn saved_direct_nested_message_uses_operand_scopes_without_reparenting_or_moving_followers() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let nested = id(&p, "nested");
|
|
let operand = id(&p, "operand");
|
|
let frame = id(&p, "fragment");
|
|
let first = id(&p, "first");
|
|
let body = occurrence(&p, first).unwrap().clone();
|
|
if let Some(SharedBody::Occurrence { body: target, .. }) =
|
|
p.elements.get_mut(&nested).unwrap().shared_mut()
|
|
{
|
|
*target = body;
|
|
}
|
|
let baseline = Scene::sequence(&p, owner, owner);
|
|
let view = saved(&mut p, owner, Some(positions(&[(nested, 380.)], &[])));
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let resolved = result(p, view);
|
|
assert_eq!(
|
|
line(&resolved.scene, nested)
|
|
.points
|
|
.iter()
|
|
.map(|point| point.y)
|
|
.collect::<Vec<_>>(),
|
|
vec![380., 380.]
|
|
);
|
|
assert_eq!(resolved.scene.rows[&nested].0, operand);
|
|
assert_eq!(resolved.scene.sequence_bounds[&operand].0, 338.);
|
|
assert_eq!(
|
|
named_box(&resolved.scene, frame).rect,
|
|
named_box(&baseline, frame).rect
|
|
);
|
|
for (id, row) in &baseline.rows {
|
|
if *id != nested {
|
|
assert_eq!(resolved.scene.rows[id], *row);
|
|
}
|
|
}
|
|
assert!(!baseline.activations.is_empty());
|
|
assert_eq!(resolved.scene.activations, baseline.activations);
|
|
passive(&document, &before, &source);
|
|
}
|
|
|
|
#[test]
|
|
fn exact_authored_lane_bits_survive_headers_and_tiny_distinct_lanes_are_not_coincident() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let left = id(&p, "left");
|
|
let right = id(&p, "right");
|
|
let first = id(&p, "first");
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[], &[(left, 0.1), (right, 400.)])),
|
|
);
|
|
let resolved = result(&p, view);
|
|
assert_eq!(resolved.lanes[&left].to_bits(), 0.1_f32.to_bits());
|
|
assert_eq!(
|
|
line(&resolved.scene, first).points[0].x.to_bits(),
|
|
0.1_f32.to_bits()
|
|
);
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[], &[(left, 0.), (right, 0.000001)])),
|
|
);
|
|
let resolved = result(&p, view);
|
|
assert_eq!(resolved.lanes[&left].to_bits(), 0_f32.to_bits());
|
|
assert_eq!(resolved.lanes[&right].to_bits(), 0.000001_f32.to_bits());
|
|
assert_ne!(resolved.lanes[&left], resolved.lanes[&right]);
|
|
let points = &line(&resolved.scene, first).points;
|
|
assert_eq!(points.len(), 2);
|
|
assert_eq!(points[1].x.to_bits(), 0.000001_f32.to_bits());
|
|
}
|
|
|
|
fn alternatives_with_death() -> (Document, Id, Id, Id, Id, Id) {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let frame = id(&p, "fragment");
|
|
let branch1 = id(&p, "operand");
|
|
let first = id(&p, "first");
|
|
let second = id(&p, "second");
|
|
let delete = id(&p, "self_reply");
|
|
let right = id(&p, "right");
|
|
let left = id(&p, "left");
|
|
// Reuse admitted occurrence identities with a second actual operand. Delete
|
|
// occupies the former call's sibling identity; the former reply is the event.
|
|
let branch2 = id(&p, "self_call");
|
|
let channel = id(&p, "channel");
|
|
if let Some(SharedBody::Scenario {
|
|
steps: occurrences, ..
|
|
}) = p.elements.get_mut(&owner).unwrap().shared_mut()
|
|
{
|
|
*occurrences = vec![frame, delete];
|
|
}
|
|
*p.elements.get_mut(&frame).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Fragment {
|
|
operator: oa_core::FragmentOperator::Alternative,
|
|
branches: vec![branch1, branch2],
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
for (branch, child) in [(branch1, first), (branch2, second)] {
|
|
let e = p.elements.get_mut(&branch).unwrap();
|
|
e.parent = Some(frame);
|
|
*e.shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Branch {
|
|
guard: "choice".into(),
|
|
occurrences: vec![child],
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
p.elements.get_mut(&child).unwrap().parent = Some(branch);
|
|
}
|
|
p.elements.remove(&id(document.project(), "nested"));
|
|
let template = occurrence(&p, first).unwrap().clone();
|
|
let mut death = template;
|
|
if let OccurrenceBody::Exchange {
|
|
exchange,
|
|
sender,
|
|
receiver,
|
|
message_kind,
|
|
reply_to,
|
|
..
|
|
} = &mut death
|
|
{
|
|
*exchange = Some(channel);
|
|
*sender = Some(left);
|
|
*receiver = Some(right);
|
|
*message_kind = MessageKind::Delete;
|
|
*reply_to = None;
|
|
}
|
|
*p.elements.get_mut(&delete).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body: death,
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
(stored(p), owner, first, second, delete, right)
|
|
}
|
|
|
|
#[test]
|
|
fn newly_skipped_sibling_scope_is_rejected_using_real_projector_diagnostics() {
|
|
let (document, owner, first, second, _, _) = alternatives_with_death();
|
|
let mut p = document.project().clone();
|
|
let baseline = Scene::sequence(&p, owner, owner);
|
|
assert_eq!(
|
|
(baseline.rows[&first].1, baseline.rows[&second].1),
|
|
(293., 475.)
|
|
);
|
|
assert!(baseline.lifecycle.missing_geometry().is_empty());
|
|
let plan = Plan::build(&p, owner, &baseline, &Candidate::from([(first, 500.)])).unwrap();
|
|
let unsupported = Scene::sequence_geometry(&p, owner, owner, None, Some(&plan));
|
|
let missing = unsupported.lifecycle.missing_geometry();
|
|
assert!(!missing.is_empty());
|
|
let view = saved(&mut p, owner, Some(positions(&[(first, 500.)], &[])));
|
|
let failures = failures(&p, view);
|
|
for (lifeline, scope) in missing {
|
|
assert!(
|
|
failures.contains(&GeometryIssue::LifecycleGeometryNotSupported { lifeline, scope })
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn post_fragment_death_cannot_disappear_and_feasible_operand_spacing_retains_its_marker() {
|
|
let (document, owner, _, second, delete, right) = alternatives_with_death();
|
|
let mut p = document.project().clone();
|
|
let baseline = Scene::sequence(&p, owner, owner);
|
|
let operand = p.elements[&second].parent.unwrap();
|
|
let fragment = p.elements[&operand].parent.unwrap();
|
|
let baseline_frame = baseline
|
|
.boxes
|
|
.iter()
|
|
.find(|item| item.id == fragment)
|
|
.unwrap()
|
|
.rect;
|
|
assert_eq!(
|
|
(baseline_frame.y, baseline_frame.y + baseline_frame.height),
|
|
(148., 567.)
|
|
);
|
|
assert_eq!(baseline.sequence_bounds[&operand], (433., 525.));
|
|
let x = baseline.render_lanes[&right];
|
|
assert_eq!(baseline.rows[&delete].1, 631.);
|
|
assert_eq!(
|
|
baseline.lifecycle.marker_at(Point::new(x, 631.)),
|
|
Some(delete)
|
|
);
|
|
let view = saved(&mut p, owner, Some(positions(&[(second, 700.)], &[])));
|
|
assert!(
|
|
failures(&p, view)
|
|
.iter()
|
|
.any(|issue| matches!(issue, GeometryIssue::LifecycleGeometryNotSupported { .. }))
|
|
);
|
|
let view = saved(&mut p, owner, Some(positions(&[(second, 520.)], &[])));
|
|
let resolved = result(&p, view);
|
|
// The message still lies above the old frame bottom, but its padded
|
|
// operand would end at 570. Retain the automatic 92px frame padding.
|
|
assert_eq!(resolved.scene.rows[&second].1, 520.);
|
|
assert_eq!(resolved.plan.scopes[&operand], (433., 570.));
|
|
let frame = resolved.plan.frames[&fragment];
|
|
assert_eq!((frame.y, frame.y + frame.height), (148., 612.));
|
|
assert_eq!(resolved.scene.sequence_bounds[&operand], (433., 570.));
|
|
assert_eq!(
|
|
resolved
|
|
.scene
|
|
.boxes
|
|
.iter()
|
|
.find(|item| item.id == fragment)
|
|
.unwrap()
|
|
.rect,
|
|
frame
|
|
);
|
|
assert!(resolved.scene.lifecycle.missing_geometry().is_empty());
|
|
assert_eq!(
|
|
resolved.scene.lifecycle.marker_at(Point::new(x, 631.)),
|
|
Some(delete)
|
|
);
|
|
assert_eq!(resolved.scene.rows[&delete].1, 631.);
|
|
}
|
|
|
|
#[test]
|
|
fn authored_send_bits_survive_baseline_delta_and_self_lifecycle_row_conversion() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let first = id(&p, "first");
|
|
for send in [0.1_f32, 0.000001_f32] {
|
|
let view = saved(&mut p, owner, Some(positions(&[(first, send)], &[])));
|
|
let resolved = result(&p, view);
|
|
assert_eq!(
|
|
resolved.plan.messages[&first].send_y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
line(&resolved.scene, first).points[0].y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
line(&resolved.scene, first).points[1].y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
}
|
|
let (document, owner) = fixture(true);
|
|
let mut p = document.project().clone();
|
|
let create = id(&p, "create_receiver");
|
|
let provider = id(&p, "provider");
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange {
|
|
sender, exchange, ..
|
|
},
|
|
..
|
|
}) = p.elements.get_mut(&create).unwrap().shared_mut()
|
|
{
|
|
*sender = Some(provider);
|
|
*exchange = None;
|
|
}
|
|
let send = f32::from_bits(64_f32.to_bits() - 1);
|
|
assert_ne!(((send + 24.) - 24.).to_bits(), send.to_bits());
|
|
let view = saved(&mut p, owner, Some(positions(&[(create, send)], &[])));
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let resolved = result(p, view);
|
|
assert_eq!(
|
|
resolved.plan.messages[&create].send_y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
line(&resolved.scene, create).points[0].y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
line(&resolved.scene, create).points[3].y.to_bits(),
|
|
(send + 24.).to_bits()
|
|
);
|
|
assert_eq!(
|
|
resolved.scene.rows[&create].1.to_bits(),
|
|
(send + 24.).to_bits()
|
|
);
|
|
passive(&document, &before, &source);
|
|
}
|
|
|
|
#[test]
|
|
fn decimal_call_anchor_is_admitted_and_exact_in_plan_line_and_typed_activation() {
|
|
let (document, owner) = fixture(false);
|
|
let mut p = document.project().clone();
|
|
let first = id(&p, "first");
|
|
let call = id(&p, "second");
|
|
let reply = id(&p, "self_reply");
|
|
let left = id(&p, "left");
|
|
let right = id(&p, "right");
|
|
for alias in ["fragment", "operand", "nested", "self_call"] {
|
|
p.elements.remove(&id(document.project(), alias));
|
|
}
|
|
*p.elements.get_mut(&owner).unwrap().steps_mut().unwrap() = vec![first, call, reply];
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { message_kind, .. },
|
|
..
|
|
}) = p.elements.get_mut(&call).unwrap().shared_mut()
|
|
{
|
|
*message_kind = MessageKind::Call;
|
|
}
|
|
let mut response = occurrence(&p, call).unwrap().clone();
|
|
if let OccurrenceBody::Exchange {
|
|
sender,
|
|
receiver,
|
|
message_kind,
|
|
reply_to,
|
|
..
|
|
} = &mut response
|
|
{
|
|
*sender = Some(right);
|
|
*receiver = Some(left);
|
|
*message_kind = MessageKind::Reply;
|
|
*reply_to = Some(call);
|
|
}
|
|
*p.elements.get_mut(&reply).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body: response,
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
let baseline = Scene::sequence(&p, owner, owner);
|
|
assert_eq!(
|
|
(
|
|
baseline.rows[&first].1,
|
|
baseline.rows[&call].1,
|
|
baseline.rows[&reply].1
|
|
),
|
|
(148., 240., 332.)
|
|
);
|
|
let send = 100.1_f32;
|
|
let view = saved(
|
|
&mut p,
|
|
owner,
|
|
Some(positions(&[(first, 90.), (call, send)], &[])),
|
|
);
|
|
let document = stored(p);
|
|
let p = document.project();
|
|
let before = p.clone();
|
|
let source = document.bundle().clone();
|
|
let resolved = result(p, view);
|
|
assert_eq!(
|
|
resolved.plan.messages[&call].send_y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
resolved.plan.messages[&call].receive_y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
line(&resolved.scene, call).points[0].y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
line(&resolved.scene, call).points[1].y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(resolved.scene.activations.len(), 1);
|
|
assert_eq!(
|
|
resolved.scene.activations[0].rect.y.to_bits(),
|
|
send.to_bits()
|
|
);
|
|
assert_eq!(
|
|
resolved.scene.activations[0].finish,
|
|
scenario_activation::Finish::Reply(reply)
|
|
);
|
|
passive(&document, &before, &source);
|
|
}
|