843 lines
28 KiB
Rust
843 lines
28 KiB
Rust
use super::*;
|
|
|
|
fn id(n: u128) -> Id {
|
|
Id::from_u128(0x21ca1100000040008000000000000000 + n)
|
|
}
|
|
struct Fixture {
|
|
project: Project,
|
|
owner: Id,
|
|
caller: Id,
|
|
callee: Id,
|
|
call: Id,
|
|
inner: Id,
|
|
reply: Id,
|
|
inner_reply: Id,
|
|
}
|
|
fn seed(p: &mut Project, layer: Layer, n: u128, kind: Kind, parent: Option<Id>) -> Id {
|
|
let mut e = p
|
|
.make_element(kind, layer, &format!("Element {n}"), parent)
|
|
.unwrap();
|
|
e.id = id(n);
|
|
e.alias = format!("element_{n}");
|
|
p.elements.insert(e.id, e);
|
|
id(n)
|
|
}
|
|
fn message(
|
|
p: &mut Project,
|
|
layer: Layer,
|
|
n: u128,
|
|
parent: Id,
|
|
endpoints: (Id, Id),
|
|
reply: Option<Id>,
|
|
) -> Id {
|
|
let e = seed(p, layer, n, Kind::Occurrence, Some(parent));
|
|
*p.elements.get_mut(&e).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange {
|
|
operation: None,
|
|
exchange: None,
|
|
sender: Some(endpoints.0),
|
|
receiver: Some(endpoints.1),
|
|
message_kind: if reply.is_some() {
|
|
MessageKind::Reply
|
|
} else {
|
|
MessageKind::Call
|
|
},
|
|
reply_to: reply,
|
|
values: vec![],
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
e
|
|
}
|
|
fn fixture(layer: Layer, reverse_uuid_order: bool) -> Fixture {
|
|
let mut p = Project::empty();
|
|
let work = if layer == Layer::Oa {
|
|
Kind::Activity
|
|
} else {
|
|
Kind::Function
|
|
};
|
|
let a = seed(&mut p, layer, 1, work, None);
|
|
let b = seed(&mut p, layer, 2, work, None);
|
|
let owner = seed(&mut p, layer, 3, Kind::Scenario, None);
|
|
if let SharedBody::Scenario { viewpoint, .. } =
|
|
p.elements.get_mut(&owner).unwrap().shared_mut().unwrap()
|
|
{
|
|
*viewpoint = if layer == Layer::Oa {
|
|
ScenarioViewpoint::OperationalActivity
|
|
} else {
|
|
ScenarioViewpoint::Functional
|
|
};
|
|
}
|
|
let caller = seed(&mut p, layer, 4, Kind::Lifeline, Some(owner));
|
|
let callee = seed(&mut p, layer, 5, Kind::Lifeline, Some(owner));
|
|
for (line, represented) in [(caller, a), (callee, b)] {
|
|
*p.elements.get_mut(&line).unwrap().shared_mut().unwrap() = SharedBody::Lifeline {
|
|
represented: Some(represented),
|
|
};
|
|
}
|
|
let (first, second) = if reverse_uuid_order {
|
|
(40, 30)
|
|
} else {
|
|
(30, 40)
|
|
};
|
|
let call = message(&mut p, layer, first, owner, (caller, callee), None);
|
|
let inner = message(&mut p, layer, second, owner, (caller, callee), None);
|
|
let reply = message(&mut p, layer, 50, owner, (callee, caller), Some(call));
|
|
let inner_reply = message(&mut p, layer, 60, owner, (callee, caller), Some(inner));
|
|
*p.elements.get_mut(&owner).unwrap().steps_mut().unwrap() =
|
|
vec![call, inner, inner_reply, reply];
|
|
Fixture {
|
|
project: p,
|
|
owner,
|
|
caller,
|
|
callee,
|
|
call,
|
|
inner,
|
|
reply,
|
|
inner_reply,
|
|
}
|
|
}
|
|
fn scene(f: &Fixture) -> Scene {
|
|
let errors: Vec<_> = validation::validate(&f.project)
|
|
.into_iter()
|
|
.filter(|i| i.severity == validation::Severity::Error)
|
|
.collect();
|
|
assert!(errors.is_empty(), "Actual core fixture: {errors:#?}");
|
|
let source = language::format(&f.project);
|
|
let reparsed = language::parse(&source).unwrap();
|
|
assert_eq!(language::format(&reparsed), source);
|
|
let result = Scene::sequence(&reparsed, f.owner, f.owner);
|
|
assert_eq!(
|
|
language::format(&reparsed),
|
|
source,
|
|
"Projection must not write geometry into the model"
|
|
);
|
|
result
|
|
}
|
|
fn bar(scene: &Scene, call: Id) -> &Segment {
|
|
scene.activations.iter().find(|a| a.call == call).unwrap()
|
|
}
|
|
|
|
fn long_labels(f: &mut Fixture) {
|
|
for occurrence in [f.call, f.inner, f.inner_reply, f.reply] {
|
|
f.project.elements.get_mut(&occurrence).unwrap().name =
|
|
"Pressure verification with a descriptive operation label".into();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn activation_shoulders_under_label_backplates_select_their_call() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = fixture(layer, true);
|
|
long_labels(&mut f);
|
|
let scene = scene(&f);
|
|
let outer = bar(&scene, f.call);
|
|
for occurrence in [f.inner, f.inner_reply, f.reply] {
|
|
let message = line(&scene, occurrence);
|
|
let shoulder = Point::new(outer.rect.x + 1., message.label_at.y + 11.);
|
|
assert!(outer.rect.contains(shoulder));
|
|
assert!(Rectangle::new(message.label_at, Size::new(225., 22.)).contains(shoulder));
|
|
assert!(message.label.chars().count() > 32);
|
|
assert!(message.points.windows(2).all(|p| {
|
|
crate::diagram::distance_to_segment(shoulder, p[0], p[1]) > 8. / scene.scale
|
|
}));
|
|
assert_eq!(scene.hit(shoulder), Some(f.call));
|
|
assert_eq!(
|
|
scene.hit(message.label_at + iced::Vector::new(4., 4.)),
|
|
Some(occurrence),
|
|
"Label text outside activations remains selectable"
|
|
);
|
|
}
|
|
for occurrence in [f.call, f.inner, f.inner_reply, f.reply] {
|
|
let message = line(&scene, occurrence);
|
|
for endpoint in [message.points[0], *message.points.last().unwrap()] {
|
|
assert_eq!(scene.hit(endpoint), Some(occurrence));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn long_message_backplates_do_not_erase_selected_activation_border_pixels() {
|
|
use iced::advanced::renderer::Headless;
|
|
use iced_runtime::user_interface::{Cache, UserInterface};
|
|
|
|
let mut mounted = crate::behavior::canvas_workspace::tests::Mounted::unshown().await;
|
|
let mut f = fixture(Layer::Oa, true);
|
|
long_labels(&mut f);
|
|
let mut scene = scene(&f);
|
|
scene.selected = f.call;
|
|
let border_x = bar(&scene, f.call).rect.x;
|
|
let label_spans: Vec<_> = [f.inner, f.inner_reply, f.reply]
|
|
.into_iter()
|
|
.map(|id| {
|
|
let message = line(&scene, id);
|
|
assert!(
|
|
Rectangle::new(message.label_at, Size::new(225., 22.))
|
|
.contains(Point::new(border_x, message.label_at.y + 11.))
|
|
);
|
|
(message.label_at.y + 3.) as usize..(message.label_at.y + 20.) as usize
|
|
})
|
|
.collect();
|
|
let dimensions = Size::new(800., 700.);
|
|
let content: Element<'_, Message> = canvas::Canvas::new(scene)
|
|
.width(dimensions.width)
|
|
.height(dimensions.height)
|
|
.into();
|
|
let mut ui = UserInterface::build(content, dimensions, Cache::new(), &mut mounted.renderer);
|
|
ui.draw(
|
|
&mut mounted.renderer,
|
|
&style::theme(),
|
|
&iced::advanced::renderer::Style {
|
|
text_color: style::INK,
|
|
},
|
|
mouse::Cursor::Unavailable,
|
|
);
|
|
let pixels = Headless::screenshot(&mut mounted.renderer, Size::new(800, 700), 1., Color::WHITE);
|
|
let expected = [style::TEAL.r, style::TEAL.g, style::TEAL.b].map(|v| v * 255.);
|
|
for span in label_spans {
|
|
for y in span {
|
|
assert!(
|
|
((border_x as usize - 2)..=(border_x as usize + 2)).any(|x| {
|
|
let offset = (y * 800 + x) * 4;
|
|
pixels[offset..offset + 3]
|
|
.iter()
|
|
.zip(expected)
|
|
.all(|(actual, target)| (*actual as f32 - target).abs() < 30.)
|
|
}),
|
|
"Selected activation border was erased at y={y}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
fn line(scene: &Scene, occurrence: Id) -> &super::super::Line {
|
|
scene.lines.iter().find(|a| a.id == occurrence).unwrap()
|
|
}
|
|
fn endpoints(p: &mut Project, occurrence: Id, from: Id, to: Id) {
|
|
if let SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange {
|
|
sender, receiver, ..
|
|
},
|
|
..
|
|
} = p
|
|
.elements
|
|
.get_mut(&occurrence)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap()
|
|
{
|
|
*sender = Some(from);
|
|
*receiver = Some(to);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn nested_calls_expose_both_bars_and_end_at_their_exact_replies_in_both_directions() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
for reverse in [false, true] {
|
|
let mut f = fixture(layer, true);
|
|
if reverse {
|
|
for occurrence in [f.call, f.inner] {
|
|
endpoints(&mut f.project, occurrence, f.callee, f.caller);
|
|
}
|
|
for occurrence in [f.reply, f.inner_reply] {
|
|
endpoints(&mut f.project, occurrence, f.caller, f.callee);
|
|
}
|
|
}
|
|
let scene = scene(&f);
|
|
let outer = bar(&scene, f.call);
|
|
let inner = bar(&scene, f.inner);
|
|
assert!(outer.rect.y < inner.rect.y && outer.bottom() > inner.bottom());
|
|
assert_eq!(outer.column, 0);
|
|
assert_eq!(inner.column, 1);
|
|
assert!(outer.rect.x < inner.rect.x && outer.edge(true) < inner.edge(true));
|
|
for (call, reply) in [(f.call, f.reply), (f.inner, f.inner_reply)] {
|
|
let execution = bar(&scene, call);
|
|
let received = *line(&scene, call).points.last().unwrap();
|
|
let returned = line(&scene, reply).points[0];
|
|
assert_eq!(execution.rect.y, received.y);
|
|
assert_eq!(execution.bottom(), returned.y);
|
|
assert_eq!(received.x, execution.edge(reverse));
|
|
assert_eq!(returned.x, execution.edge(reverse));
|
|
assert_eq!(execution.finish, Finish::Reply(reply));
|
|
}
|
|
assert!(scene.open_calls.is_empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn call_uuid_order_does_not_choose_activation_columns_or_message_anchors() {
|
|
let f = fixture(Layer::Oa, false);
|
|
let g = fixture(Layer::Oa, true);
|
|
let a = scene(&f);
|
|
let b = scene(&g);
|
|
for (x, y) in [
|
|
(f.call, g.call),
|
|
(f.inner, g.inner),
|
|
(f.reply, g.reply),
|
|
(f.inner_reply, g.inner_reply),
|
|
] {
|
|
assert_eq!(line(&a, x).points, line(&b, y).points);
|
|
}
|
|
for (x, y) in [(f.call, g.call), (f.inner, g.inner)] {
|
|
assert_eq!(bar(&a, x).rect, bar(&b, y).rect);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sequential_calls_reuse_column_and_crossed_replies_keep_their_correlations() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = fixture(layer, true);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = vec![f.call, f.reply, f.inner, f.inner_reply];
|
|
let sequential = scene(&f);
|
|
assert_eq!(
|
|
bar(&sequential, f.call).rect.x,
|
|
bar(&sequential, f.inner).rect.x
|
|
);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = vec![f.call, f.inner, f.reply, f.inner_reply];
|
|
let crossed = scene(&f);
|
|
assert!(bar(&crossed, f.call).bottom() < bar(&crossed, f.inner).bottom());
|
|
assert!(bar(&crossed, f.call).rect.x < bar(&crossed, f.inner).rect.x);
|
|
assert_eq!(
|
|
line(&crossed, f.reply).points[0].x,
|
|
bar(&crossed, f.call).edge(false)
|
|
);
|
|
assert_eq!(
|
|
line(&crossed, f.inner_reply).points[0].x,
|
|
bar(&crossed, f.inner).edge(false)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn self_call_receives_at_lower_loop_end_and_self_reply_returns_to_the_parent() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = fixture(layer, true);
|
|
endpoints(&mut f.project, f.inner, f.callee, f.callee);
|
|
endpoints(&mut f.project, f.inner_reply, f.callee, f.callee);
|
|
let scene = scene(&f);
|
|
let parent = bar(&scene, f.call);
|
|
let child = bar(&scene, f.inner);
|
|
let call = line(&scene, f.inner);
|
|
let reply = line(&scene, f.inner_reply);
|
|
assert_eq!(call.points[0].x, parent.edge(true));
|
|
assert_eq!(call.points[3], Point::new(child.edge(true), child.rect.y));
|
|
assert_eq!(child.rect.y, scene.rows[&f.inner].1 + 24.);
|
|
assert_eq!(
|
|
reply.points[0],
|
|
Point::new(child.edge(true), child.bottom())
|
|
);
|
|
assert_eq!(reply.points[3].x, parent.edge(true));
|
|
assert!(call.points[1].x > child.edge(true));
|
|
assert!(reply.points[1].x > child.edge(true));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn an_inactive_self_call_has_no_fabricated_parent_and_three_nested_calls_expose_three_columns() {
|
|
let mut f = fixture(Layer::Oa, true);
|
|
endpoints(&mut f.project, f.inner, f.callee, f.callee);
|
|
endpoints(&mut f.project, f.inner_reply, f.callee, f.callee);
|
|
f.project.elements.remove(&f.call);
|
|
f.project.elements.remove(&f.reply);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = vec![f.inner, f.inner_reply];
|
|
let isolated = scene(&f);
|
|
assert_eq!(isolated.activations.len(), 1);
|
|
assert_eq!(bar(&isolated, f.inner).column, 0);
|
|
assert_eq!(
|
|
line(&isolated, f.inner).points[0].x,
|
|
isolated.lanes[&f.callee]
|
|
);
|
|
assert_eq!(
|
|
line(&isolated, f.inner_reply).points[3].x,
|
|
isolated.lanes[&f.callee]
|
|
);
|
|
assert_eq!(
|
|
bar(&isolated, f.inner).rect.y,
|
|
line(&isolated, f.inner).points[3].y
|
|
);
|
|
|
|
let mut f = fixture(Layer::Oa, true);
|
|
let third = message(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
20,
|
|
f.owner,
|
|
(f.caller, f.callee),
|
|
None,
|
|
);
|
|
let third_reply = message(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
21,
|
|
f.owner,
|
|
(f.callee, f.caller),
|
|
Some(third),
|
|
);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = vec![f.call, f.inner, third, third_reply, f.inner_reply, f.reply];
|
|
let scene = scene(&f);
|
|
let calls: Vec<_> = scene.activations.iter().map(|a| a.call).collect();
|
|
assert_eq!(
|
|
calls,
|
|
vec![f.call, f.inner, third],
|
|
"Paint order is shallow to deep"
|
|
);
|
|
for (column, call) in [f.call, f.inner, third].into_iter().enumerate() {
|
|
let execution = bar(&scene, call);
|
|
assert_eq!(execution.column, column);
|
|
let y = (bar(&scene, third).rect.y + bar(&scene, third).bottom()) / 2.;
|
|
assert_eq!(
|
|
scene.hit(Point::new(execution.rect.x + OFFSET / 2., y)),
|
|
Some(call)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reentrant_call_uses_the_other_receiver_without_inventing_a_caller_execution() {
|
|
let mut f = fixture(Layer::Oa, true);
|
|
endpoints(&mut f.project, f.inner, f.callee, f.caller);
|
|
endpoints(&mut f.project, f.inner_reply, f.caller, f.callee);
|
|
let scene = scene(&f);
|
|
assert_eq!(bar(&scene, f.call).column, 0);
|
|
assert_eq!(bar(&scene, f.inner).column, 0);
|
|
assert_eq!(bar(&scene, f.inner).receiver, f.caller);
|
|
assert_eq!(line(&scene, f.call).points[0].x, scene.lanes[&f.caller]);
|
|
assert_eq!(
|
|
line(&scene, f.inner).points[0].x,
|
|
bar(&scene, f.call).edge(false)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn actual_canvas_pointer_selects_each_exposed_shoulder_under_zoom_and_pan() {
|
|
let f = fixture(Layer::Oa, true);
|
|
let mut scene = scene(&f);
|
|
let context = scenario::direct_tests::context(f.owner);
|
|
scene.interaction = Some((context, scenario::CanvasTool::Select, true));
|
|
let y = (bar(&scene, f.inner).rect.y + bar(&scene, f.inner).bottom()) / 2.;
|
|
let outer = Point::new(bar(&scene, f.call).rect.x + OFFSET / 2., y);
|
|
let inner = Point::new(bar(&scene, f.inner).edge(true) - OFFSET / 2., y);
|
|
for scale in [1., 0.25, 0.05] {
|
|
scene.scale = scale;
|
|
scene.camera_pan = iced::Vector::new(50., 25.);
|
|
let bounds = Rectangle::new(Point::new(30., 40.), Size::new(1000., 700.));
|
|
for (point, call) in [(outer, f.call), (inner, f.inner)] {
|
|
assert_eq!(scene.hit(point), Some(call));
|
|
let cursor = bounds.position()
|
|
+ scene.camera_pan
|
|
+ iced::Vector::new(point.x * scale, point.y * scale);
|
|
let mut state = SequenceInput::default();
|
|
let _ = canvas::Program::update(
|
|
&scene,
|
|
&mut state,
|
|
&canvas::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
|
|
bounds,
|
|
mouse::Cursor::Available(cursor),
|
|
);
|
|
let action = canvas::Program::update(
|
|
&scene,
|
|
&mut state,
|
|
&canvas::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)),
|
|
bounds,
|
|
mouse::Cursor::Available(cursor),
|
|
)
|
|
.unwrap();
|
|
assert!(matches!(action.into_inner().0,
|
|
Some(Message::ScenarioCanvas(scenario::CanvasEvent::Select(_, selected))) if selected == call));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn missing_reply_is_a_short_explicit_open_span_and_an_uncorrelated_reply_closes_nothing() {
|
|
let mut f = fixture(Layer::Oa, true);
|
|
if let SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { reply_to, .. },
|
|
..
|
|
} = f
|
|
.project
|
|
.elements
|
|
.get_mut(&f.reply)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap()
|
|
{
|
|
*reply_to = None;
|
|
}
|
|
let scene = scene(&f);
|
|
assert_eq!(bar(&scene, f.call).finish, Finish::Open);
|
|
assert_eq!(bar(&scene, f.call).rect.height, 26.);
|
|
assert_eq!(scene.open_calls.len(), 1);
|
|
assert_eq!(
|
|
bar(&scene, f.inner).column,
|
|
0,
|
|
"Unknown duration does not prove later nesting"
|
|
);
|
|
assert_eq!(bar(&scene, f.inner).finish, Finish::Reply(f.inner_reply));
|
|
}
|
|
|
|
#[test]
|
|
fn alternative_and_parallel_operand_local_calls_do_not_nest_from_screen_order() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
for operator in [FragmentOperator::Alternative, FragmentOperator::Parallel] {
|
|
let mut f = fixture(layer, true);
|
|
let fragment = seed(&mut f.project, layer, 70, Kind::Occurrence, Some(f.owner));
|
|
let branches = [id(71), id(72)];
|
|
*f.project
|
|
.elements
|
|
.get_mut(&fragment)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Fragment {
|
|
operator,
|
|
branches: branches.to_vec(),
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
for (index, occurrences) in [vec![f.call, f.reply], vec![f.inner, f.inner_reply]]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
let branch = seed(
|
|
&mut f.project,
|
|
layer,
|
|
71 + index as u128,
|
|
Kind::Occurrence,
|
|
Some(fragment),
|
|
);
|
|
for occurrence in &occurrences {
|
|
f.project.elements.get_mut(occurrence).unwrap().parent = Some(branch);
|
|
}
|
|
*f.project
|
|
.elements
|
|
.get_mut(&branch)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Branch {
|
|
guard: format!("condition_{index}"),
|
|
occurrences,
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
}
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = vec![fragment];
|
|
let scene = scene(&f);
|
|
assert_eq!(bar(&scene, f.call).column, 0);
|
|
assert_eq!(bar(&scene, f.inner).column, 0);
|
|
assert_ne!(bar(&scene, f.call).scope, bar(&scene, f.inner).scope);
|
|
assert!(scene.open_calls.is_empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn inherited_execution_anchors_a_self_call_inside_an_operand_before_returning_after_the_fragment() {
|
|
let mut f = fixture(Layer::Oa, true);
|
|
endpoints(&mut f.project, f.inner, f.callee, f.callee);
|
|
endpoints(&mut f.project, f.inner_reply, f.callee, f.callee);
|
|
let fragment = seed(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
70,
|
|
Kind::Occurrence,
|
|
Some(f.owner),
|
|
);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&fragment)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Fragment {
|
|
operator: FragmentOperator::Alternative,
|
|
branches: vec![id(71), id(72)],
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
for (index, occurrences) in [vec![f.inner, f.inner_reply], vec![]]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
let branch = seed(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
71 + index as u128,
|
|
Kind::Occurrence,
|
|
Some(fragment),
|
|
);
|
|
for occurrence in &occurrences {
|
|
f.project.elements.get_mut(occurrence).unwrap().parent = Some(branch);
|
|
}
|
|
*f.project
|
|
.elements
|
|
.get_mut(&branch)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Branch {
|
|
guard: format!("condition_{index}"),
|
|
occurrences,
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
}
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = vec![f.call, fragment, f.reply];
|
|
let scene = scene(&f);
|
|
let parent = scene
|
|
.activations
|
|
.iter()
|
|
.find(|a| a.call == f.call && a.scope == id(71))
|
|
.unwrap();
|
|
assert_eq!(parent.finish, Finish::Continuation);
|
|
assert_eq!(bar(&scene, f.inner).column, parent.column + 1);
|
|
assert_eq!(line(&scene, f.inner).points[0].x, parent.edge(true));
|
|
assert_eq!(line(&scene, f.inner_reply).points[3].x, parent.edge(true));
|
|
assert!(
|
|
scene
|
|
.activations
|
|
.iter()
|
|
.any(|a| a.call == f.call && a.finish == Finish::Reply(f.reply))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_fragment_cannot_make_crossed_calls_an_unambiguous_caller_execution() {
|
|
for placement in ["plain", "before_fragment", "in_operand"] {
|
|
let mut f = fixture(Layer::Oa, true);
|
|
let outgoing = message(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
80,
|
|
f.owner,
|
|
(f.callee, f.caller),
|
|
None,
|
|
);
|
|
if let SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { message_kind, .. },
|
|
..
|
|
} = f
|
|
.project
|
|
.elements
|
|
.get_mut(&outgoing)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap()
|
|
{
|
|
*message_kind = MessageKind::Asynchronous;
|
|
}
|
|
let mut ordered = vec![f.call, f.inner];
|
|
if placement != "in_operand" {
|
|
ordered.push(outgoing);
|
|
}
|
|
if placement != "plain" {
|
|
let fragment = seed(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
70,
|
|
Kind::Occurrence,
|
|
Some(f.owner),
|
|
);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&fragment)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Fragment {
|
|
operator: FragmentOperator::Alternative,
|
|
branches: vec![id(71), id(72)],
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
for index in 0..2u128 {
|
|
let branch = seed(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
71 + index,
|
|
Kind::Occurrence,
|
|
Some(fragment),
|
|
);
|
|
let occurrences = if index == 0 && placement == "in_operand" {
|
|
f.project.elements.get_mut(&outgoing).unwrap().parent = Some(branch);
|
|
vec![outgoing]
|
|
} else {
|
|
vec![]
|
|
};
|
|
*f.project
|
|
.elements
|
|
.get_mut(&branch)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Branch {
|
|
guard: format!("condition_{index}"),
|
|
occurrences,
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
}
|
|
ordered.push(fragment);
|
|
}
|
|
ordered.extend([f.reply, f.inner_reply]);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = ordered;
|
|
let scene = scene(&f);
|
|
assert_eq!(
|
|
line(&scene, outgoing).points[0].x,
|
|
scene.lanes[&f.callee],
|
|
"{placement}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn nested_correlations_returning_in_alternative_operands_remain_nested_before_the_fragment() {
|
|
let mut f = fixture(Layer::Oa, true);
|
|
let outgoing = message(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
80,
|
|
f.owner,
|
|
(f.callee, f.caller),
|
|
None,
|
|
);
|
|
if let SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { message_kind, .. },
|
|
..
|
|
} = f
|
|
.project
|
|
.elements
|
|
.get_mut(&outgoing)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap()
|
|
{
|
|
*message_kind = MessageKind::Asynchronous;
|
|
}
|
|
let fragment = seed(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
70,
|
|
Kind::Occurrence,
|
|
Some(f.owner),
|
|
);
|
|
*f.project
|
|
.elements
|
|
.get_mut(&fragment)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Fragment {
|
|
operator: FragmentOperator::Alternative,
|
|
branches: vec![id(71), id(72)],
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
for index in 0..2u128 {
|
|
let branch = seed(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
71 + index,
|
|
Kind::Occurrence,
|
|
Some(fragment),
|
|
);
|
|
let occurrences = if index == 0 {
|
|
for reply in [f.inner_reply, f.reply] {
|
|
f.project.elements.get_mut(&reply).unwrap().parent = Some(branch);
|
|
}
|
|
vec![f.inner_reply, f.reply]
|
|
} else {
|
|
vec![
|
|
message(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
90,
|
|
branch,
|
|
(f.callee, f.caller),
|
|
Some(f.inner),
|
|
),
|
|
message(
|
|
&mut f.project,
|
|
Layer::Oa,
|
|
91,
|
|
branch,
|
|
(f.callee, f.caller),
|
|
Some(f.call),
|
|
),
|
|
]
|
|
};
|
|
*f.project
|
|
.elements
|
|
.get_mut(&branch)
|
|
.unwrap()
|
|
.shared_mut()
|
|
.unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::Branch {
|
|
guard: format!("condition_{index}"),
|
|
occurrences,
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
}
|
|
*f.project
|
|
.elements
|
|
.get_mut(&f.owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap() = vec![f.call, f.inner, outgoing, fragment];
|
|
let scene = scene(&f);
|
|
let inner = scene
|
|
.activations
|
|
.iter()
|
|
.find(|a| a.call == f.inner && a.scope == f.owner)
|
|
.unwrap();
|
|
assert_eq!(line(&scene, outgoing).points[0].x, inner.edge(false));
|
|
}
|