Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
1295 lines
42 KiB
Rust
1295 lines
42 KiB
Rust
use oa_core::{
|
|
operations::*,
|
|
topology_context::*,
|
|
transitions::*,
|
|
validation::{self, Severity},
|
|
*,
|
|
};
|
|
|
|
fn valid(p: &Project) {
|
|
let errors: Vec<_> = validation::validate(p)
|
|
.into_iter()
|
|
.filter(|i| i.severity == Severity::Error)
|
|
.collect();
|
|
assert!(errors.is_empty(), "{errors:?}");
|
|
}
|
|
fn occurrence(p: &mut Project, owner: Id, name: &str, body: OccurrenceBody) -> Id {
|
|
let mut e = p
|
|
.make_element(
|
|
Kind::Occurrence,
|
|
p.elements[&owner].layer(),
|
|
name,
|
|
Some(owner),
|
|
)
|
|
.unwrap();
|
|
*e.shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body,
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
let id = e.id;
|
|
p.elements.insert(id, e);
|
|
p.elements
|
|
.get_mut(&owner)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap()
|
|
.push(id);
|
|
id
|
|
}
|
|
fn endpoint(path: &[Id], occurrence: Id) -> OccurrenceEndpoint {
|
|
OccurrenceEndpoint {
|
|
references: path.to_vec(),
|
|
occurrence,
|
|
}
|
|
}
|
|
struct Chain {
|
|
p: Project,
|
|
outer: Id,
|
|
procedure: Id,
|
|
a: Id,
|
|
b: Id,
|
|
x: Id,
|
|
r1: Id,
|
|
r2: Id,
|
|
inner: Id,
|
|
}
|
|
fn chain(layer: Layer) -> Chain {
|
|
let mut p = Project::empty();
|
|
let kind = if layer == Layer::Oa {
|
|
Kind::Process
|
|
} else {
|
|
Kind::FunctionalChain
|
|
};
|
|
let work = if layer == Layer::Oa {
|
|
Kind::Activity
|
|
} else {
|
|
Kind::Function
|
|
};
|
|
let outer = p.add_element(kind, layer, "Caller", None).unwrap();
|
|
let procedure = p
|
|
.add_element(kind, layer, "Shared procedure", None)
|
|
.unwrap();
|
|
let nested = p.add_element(kind, layer, "Nested caller", None).unwrap();
|
|
let mut actions = vec![];
|
|
for (owner, name) in [
|
|
(procedure, "A"),
|
|
(procedure, "B"),
|
|
(procedure, "C"),
|
|
(outer, "X"),
|
|
] {
|
|
let action = p.add_element(work, layer, name, None).unwrap();
|
|
actions.push(occurrence(
|
|
&mut p,
|
|
owner,
|
|
name,
|
|
OccurrenceBody::Action {
|
|
action: Some(action),
|
|
lifeline: None,
|
|
},
|
|
));
|
|
}
|
|
let [a, b, c, x] = actions.try_into().unwrap();
|
|
p.relate(RelationKind::SequenceLink, "A then B", a, b);
|
|
p.relate(RelationKind::SequenceLink, "B then C", b, c);
|
|
let r1 = occurrence(
|
|
&mut p,
|
|
outer,
|
|
"First reuse",
|
|
OccurrenceBody::SequenceReference {
|
|
sequence: Some(procedure),
|
|
},
|
|
);
|
|
let r2 = occurrence(
|
|
&mut p,
|
|
outer,
|
|
"Second reuse",
|
|
OccurrenceBody::SequenceReference {
|
|
sequence: Some(procedure),
|
|
},
|
|
);
|
|
let inner = occurrence(
|
|
&mut p,
|
|
nested,
|
|
"Nested reuse",
|
|
OccurrenceBody::SequenceReference {
|
|
sequence: Some(procedure),
|
|
},
|
|
);
|
|
valid(&p);
|
|
Chain {
|
|
p,
|
|
outer,
|
|
procedure,
|
|
a,
|
|
b,
|
|
x,
|
|
r1,
|
|
r2,
|
|
inner,
|
|
}
|
|
}
|
|
fn link(f: &Chain, purpose: ContextualLinkPurpose) -> Relation {
|
|
Relation {
|
|
id: Id::new_v4(),
|
|
alias: "caller_connection".into(),
|
|
name: "Caller contribution".into(),
|
|
description: String::new(),
|
|
body: RelationBody::ContextualLink {
|
|
owner: f.outer,
|
|
source: endpoint(&[], f.x),
|
|
target: endpoint(&[f.r1], f.b),
|
|
purpose,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn source7_repeated_nested_qualified_precedence_and_cache_are_distinct() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = chain(layer);
|
|
let relation = link(
|
|
&f,
|
|
ContextualLinkPurpose::Precedence {
|
|
guard: "authorized".into(),
|
|
},
|
|
);
|
|
f.p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
|
|
let index = TopologyIndex::with_cache_capacity(&f.p, 1);
|
|
let one = index.resolve(f.outer, &endpoint(&[f.r1], f.b)).unwrap();
|
|
let two = index.resolve(f.outer, &endpoint(&[f.r2], f.b)).unwrap();
|
|
assert_ne!(one.address, two.address);
|
|
assert_eq!(index.resolve(f.outer, &one.address.endpoint).unwrap(), one);
|
|
let analysis = index.analyze(f.outer, AnalysisLimits::default());
|
|
assert!(analysis.complete);
|
|
let warnings: Vec<_> = analysis
|
|
.notices
|
|
.iter()
|
|
.filter(|n| n.code == "ambiguous_sequence_branch")
|
|
.collect();
|
|
assert_eq!(warnings.len(), 1);
|
|
assert_eq!(warnings[0].address, one.address);
|
|
assert!(
|
|
!analysis
|
|
.precedence
|
|
.iter()
|
|
.any(|e| e.source.address.endpoint.occurrence == f.x
|
|
&& e.target.address.endpoint == endpoint(&[f.r1], f.a))
|
|
);
|
|
assert!(
|
|
analysis
|
|
.precedence
|
|
.iter()
|
|
.any(|e| e.relation == Some(relation.id) && e.target.address == one.address)
|
|
);
|
|
assert!(
|
|
index
|
|
.analyze(f.procedure, AnalysisLimits::default())
|
|
.notices
|
|
.is_empty()
|
|
);
|
|
let limited = index.analyze(f.outer, AnalysisLimits { nodes: 1, edges: 1 });
|
|
assert!(!limited.complete);
|
|
assert!(
|
|
limited
|
|
.notices
|
|
.iter()
|
|
.any(|n| n.code == "context_analysis_limit")
|
|
);
|
|
assert!(index.analyze(f.outer, AnalysisLimits::default()).complete);
|
|
let nested_owner = f.p.elements[&f.inner].parent.unwrap();
|
|
let mut changed = f.p.elements[&f.r1].clone();
|
|
*changed.shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body: OccurrenceBody::SequenceReference {
|
|
sequence: Some(nested_owner),
|
|
},
|
|
precondition: String::new(),
|
|
postcondition: String::new(),
|
|
};
|
|
assert!(execute_batch(&f.p, &[Command::EditElement(f.r1, changed.clone())]).is_err());
|
|
let mut repaired = relation.clone();
|
|
if let RelationBody::ContextualLink { target, .. } = &mut repaired.body {
|
|
target.references.push(f.inner);
|
|
}
|
|
let nested = execute_batch(
|
|
&f.p,
|
|
&[
|
|
Command::EditElement(f.r1, changed),
|
|
Command::EditRelation(relation.id, repaired),
|
|
],
|
|
)
|
|
.unwrap();
|
|
assert!(
|
|
TopologyIndex::new(&nested)
|
|
.resolve(f.outer, &endpoint(&[f.r1, f.inner], f.b))
|
|
.is_ok()
|
|
);
|
|
assert!(
|
|
TopologyIndex::new(&nested)
|
|
.resolve(f.outer, &endpoint(&[f.r2, f.inner], f.b))
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|
|
#[test]
|
|
fn source7_context_lifecycle_source_history_and_closed_admission() {
|
|
let f = chain(Layer::Oa);
|
|
let relation = link(
|
|
&f,
|
|
ContextualLinkPurpose::Precedence {
|
|
guard: "ready".into(),
|
|
},
|
|
);
|
|
let mut session = Session::new(f.p.clone());
|
|
session
|
|
.execute(Command::CreateRelation(relation.clone()))
|
|
.unwrap();
|
|
let after = session.project().clone();
|
|
let source = language::format(&after);
|
|
assert_eq!(language::parse(&source).unwrap(), after);
|
|
assert_eq!(
|
|
persistence::decode(&serde_json::to_vec(&after).unwrap()).unwrap(),
|
|
after
|
|
);
|
|
session.undo();
|
|
assert_eq!(session.project(), &f.p);
|
|
session.redo();
|
|
assert_eq!(session.project(), &after);
|
|
session.execute(Command::Delete(f.r1)).unwrap();
|
|
assert!(!session.project().relations.contains_key(&relation.id));
|
|
session.undo();
|
|
assert_eq!(session.project(), &after);
|
|
let fresh = Id::new_v4();
|
|
let mut reidentified = after.clone();
|
|
references::remap_references(&mut reidentified, f.r1, fresh);
|
|
let mut e = reidentified.elements.remove(&f.r1).unwrap();
|
|
e.id = fresh;
|
|
reidentified.elements.insert(fresh, e);
|
|
valid(&reidentified);
|
|
assert!(
|
|
TopologyIndex::new(&reidentified)
|
|
.resolve(f.outer, &endpoint(&[fresh], f.b))
|
|
.is_ok()
|
|
);
|
|
for version in [4, 5, 6] {
|
|
let old = source.replacen("arcadia 7;", &format!("arcadia {version};"), 1);
|
|
assert!(language::parse(&old).is_err());
|
|
let mut json = serde_json::to_value(&after).unwrap();
|
|
json["schema_version"] = version.into();
|
|
assert!(persistence::decode(&serde_json::to_vec(&json).unwrap()).is_err());
|
|
}
|
|
let mut direct = relation.clone();
|
|
if let RelationBody::ContextualLink { target, .. } = &mut direct.body {
|
|
*target = endpoint(&[], f.x);
|
|
}
|
|
assert!(execute_batch(&f.p, &[Command::CreateRelation(direct)]).is_err());
|
|
}
|
|
|
|
fn operation_id(n: u128) -> Id {
|
|
Id::from_u128(0x52060906000040008000000000000000 + n)
|
|
}
|
|
fn operations_fixture() -> Project {
|
|
persistence::decode(include_bytes!(
|
|
"../../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
|
))
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn source7_operation_reverse_unoriented_call_and_reply_use_actual_binding() {
|
|
let mut p = operations_fixture();
|
|
p.relations
|
|
.get_mut(&operation_id(34))
|
|
.unwrap()
|
|
.body
|
|
.set_endpoints(operation_id(13), operation_id(12));
|
|
for (call, allocation) in [
|
|
(operation_id(43), operation_id(30)),
|
|
(operation_id(44), operation_id(31)),
|
|
] {
|
|
p.elements
|
|
.insert(call, operation_edit(&p, call, Some(allocation)));
|
|
}
|
|
let reply = occurrence(
|
|
&mut p,
|
|
operation_id(40),
|
|
"Exact reset reply",
|
|
OccurrenceBody::Exchange {
|
|
exchange: Some(operation_id(34)),
|
|
sender: Some(operation_id(42)),
|
|
receiver: Some(operation_id(41)),
|
|
message_kind: MessageKind::Reply,
|
|
reply_to: Some(operation_id(43)),
|
|
values: vec![],
|
|
operation: None,
|
|
},
|
|
);
|
|
valid(&p);
|
|
for message in [operation_id(43), reply] {
|
|
let OperationResolution::Selected(proof) = resolve(&p, message).unwrap() else {
|
|
panic!()
|
|
};
|
|
assert_eq!(proof.invocation, operation_id(43));
|
|
assert_eq!(proof.allocation, operation_id(30));
|
|
assert_eq!(
|
|
(proof.caller_port, proof.provider_port),
|
|
(operation_id(12), operation_id(13))
|
|
);
|
|
}
|
|
let mut other = p.elements[&reply].clone();
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { reply_to, .. },
|
|
..
|
|
}) = other.shared_mut()
|
|
{
|
|
*reply_to = Some(operation_id(44));
|
|
}
|
|
let q = execute_batch(&p, &[Command::EditElement(reply, other)]).unwrap();
|
|
let OperationResolution::Selected(proof) = resolve(&q, reply).unwrap() else {
|
|
panic!()
|
|
};
|
|
assert_eq!(proof.invocation, operation_id(44));
|
|
assert_eq!(proof.allocation, operation_id(31));
|
|
let mut oriented = p.relations[&operation_id(34)].clone();
|
|
if let RelationBody::BoundaryExchange { oriented, .. } = &mut oriented.body {
|
|
*oriented = true;
|
|
}
|
|
assert!(execute_batch(&p, &[Command::EditRelation(oriented.id, oriented)]).is_err());
|
|
assert_eq!(
|
|
persistence::decode(&serde_json::to_vec(&q).unwrap()).unwrap(),
|
|
q
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn source7_operation_inherited_exact_allocation_is_not_a_same_item_substitute() {
|
|
let mut p = operations_fixture();
|
|
let derived = p
|
|
.add_element(
|
|
Kind::Interface,
|
|
Layer::Sa,
|
|
"Specialized provider service",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
*p.elements.get_mut(&derived).unwrap().shared_mut().unwrap() = SharedBody::Interface {
|
|
supertype: Some(operation_id(22)),
|
|
};
|
|
p.relations
|
|
.get_mut(&operation_id(33))
|
|
.unwrap()
|
|
.body
|
|
.set_endpoints(operation_id(13), derived);
|
|
let call = operation_id(43);
|
|
p.elements
|
|
.insert(call, operation_edit(&p, call, Some(operation_id(30))));
|
|
valid(&p);
|
|
let OperationResolution::Selected(proof) = resolve(&p, call).unwrap() else {
|
|
panic!()
|
|
};
|
|
assert_eq!(proof.allocation, operation_id(30));
|
|
let other = p
|
|
.add_element(Kind::Interface, Layer::Sa, "Separate reset contract", None)
|
|
.unwrap();
|
|
let mut separate = p.relations[&operation_id(30)].clone();
|
|
separate.id = Id::new_v4();
|
|
separate.alias = "separately_allocated_reset".into();
|
|
separate.body.set_endpoints(other, operation_id(20));
|
|
let separate_id = separate.id;
|
|
p.relations.insert(separate.id, separate);
|
|
p.relate(
|
|
RelationKind::InterfaceProvision,
|
|
"Also provides separate reset",
|
|
operation_id(13),
|
|
other,
|
|
);
|
|
valid(&p);
|
|
// Same item and actual provider are insufficient: the caller requires only
|
|
// the inherited allocation's interface, not this separately allocated one.
|
|
let candidate = operation_edit(&p, call, Some(separate_id));
|
|
let mut probe = p.clone();
|
|
probe.elements.insert(call, candidate.clone());
|
|
assert_eq!(resolve(&probe, call).unwrap_err().code, "operation_roles");
|
|
assert!(execute_batch(&p, &[Command::EditElement(call, candidate.clone())]).is_err());
|
|
p.relate(
|
|
RelationKind::InterfaceRequirement,
|
|
"Explicitly requires separate reset",
|
|
operation_id(12),
|
|
other,
|
|
);
|
|
let q = execute_batch(&p, &[Command::EditElement(call, candidate)]).unwrap();
|
|
let OperationResolution::Selected(proof) = resolve(&q, call).unwrap() else {
|
|
panic!()
|
|
};
|
|
assert_eq!(proof.allocation, separate_id);
|
|
assert_eq!(proof.item, operation_id(20));
|
|
}
|
|
|
|
#[test]
|
|
fn source7_operation_transmission_matrix_rejects_mismatch_and_warns_unspecified() {
|
|
for kind in [MessageKind::Call, MessageKind::Asynchronous] {
|
|
for protocol in [
|
|
TransmissionProtocol::Synchronous,
|
|
TransmissionProtocol::Asynchronous,
|
|
TransmissionProtocol::Unspecified,
|
|
] {
|
|
let p = operations_fixture();
|
|
let call = operation_id(43);
|
|
let mut candidate = operation_edit(&p, call, Some(operation_id(30)));
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { message_kind, .. },
|
|
..
|
|
}) = candidate.shared_mut()
|
|
{
|
|
*message_kind = kind.clone();
|
|
}
|
|
let mut allocation = p.relations[&operation_id(30)].clone();
|
|
if let RelationBody::InterfaceItemAllocation { transmission, .. } = &mut allocation.body
|
|
{
|
|
*transmission = protocol;
|
|
}
|
|
let commands = [
|
|
Command::EditElement(call, candidate.clone()),
|
|
Command::EditRelation(allocation.id, allocation.clone()),
|
|
];
|
|
let allowed = matches!(
|
|
(&kind, protocol),
|
|
(_, TransmissionProtocol::Unspecified)
|
|
| (MessageKind::Call, TransmissionProtocol::Synchronous)
|
|
| (
|
|
MessageKind::Asynchronous,
|
|
TransmissionProtocol::Asynchronous
|
|
)
|
|
);
|
|
let result = execute_batch(&p, &commands);
|
|
if allowed {
|
|
let q = result.unwrap();
|
|
assert!(matches!(
|
|
resolve(&q, call),
|
|
Ok(OperationResolution::Selected(_))
|
|
));
|
|
let warned = validation::validate(&q).iter().any(|i| {
|
|
i.code == "operation_transmission_unspecified"
|
|
&& i.severity == Severity::Warning
|
|
});
|
|
assert_eq!(warned, protocol == TransmissionProtocol::Unspecified);
|
|
} else {
|
|
assert!(result.is_err(), "{kind:?} with {protocol:?}");
|
|
let mut probe = p.clone();
|
|
probe.elements.insert(call, candidate);
|
|
probe.relations.insert(allocation.id, allocation);
|
|
assert_eq!(
|
|
resolve(&probe, call).unwrap_err().code,
|
|
"operation_transmission"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn source7_operation_allocation_delete_repairs_both_call_and_separate_reply_channels() {
|
|
let mut p = operations_fixture();
|
|
let call = operation_id(43);
|
|
let allocation = operation_id(30);
|
|
let field = p
|
|
.add_element(
|
|
Kind::DataField,
|
|
Layer::Sa,
|
|
"Retained bidirectional value",
|
|
Some(operation_id(20)),
|
|
)
|
|
.unwrap();
|
|
if let Some(SharedBody::DataField { direction, .. }) =
|
|
p.elements.get_mut(&field).unwrap().shared_mut()
|
|
{
|
|
*direction = ParameterDirection::InOut;
|
|
}
|
|
let value = MessageValue {
|
|
path: vec![],
|
|
field,
|
|
expression: "unfinished(\n preserve exactly".into(),
|
|
};
|
|
let mut invocation = operation_edit(&p, call, Some(allocation));
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { values, .. },
|
|
..
|
|
}) = invocation.shared_mut()
|
|
{
|
|
values.push(value.clone());
|
|
}
|
|
p.elements.insert(call, invocation);
|
|
let mut response = p.relations[&operation_id(34)].clone();
|
|
response.id = Id::new_v4();
|
|
response.alias = "explicit_reset_response".into();
|
|
response
|
|
.body
|
|
.set_endpoints(operation_id(13), operation_id(12));
|
|
if let RelationBody::BoundaryExchange { items, .. } = &mut response.body {
|
|
*items = vec![operation_id(20)];
|
|
}
|
|
let response_id = response.id;
|
|
p.relations.insert(response.id, response);
|
|
let reply = occurrence(
|
|
&mut p,
|
|
operation_id(40),
|
|
"Explicit reset response",
|
|
OccurrenceBody::Exchange {
|
|
exchange: Some(response_id),
|
|
sender: Some(operation_id(42)),
|
|
receiver: Some(operation_id(41)),
|
|
message_kind: MessageKind::Reply,
|
|
reply_to: Some(call),
|
|
values: vec![value.clone()],
|
|
operation: None,
|
|
},
|
|
);
|
|
valid(&p);
|
|
let repair = |id| {
|
|
let mut channel = p.relations[&id].clone();
|
|
if let RelationBody::BoundaryExchange { items, .. } = &mut channel.body {
|
|
items.retain(|item| *item != operation_id(20));
|
|
}
|
|
Command::EditRelation(id, channel)
|
|
};
|
|
let mut session = Session::new(p.clone());
|
|
let source = language::format(&p);
|
|
assert!(
|
|
session
|
|
.execute(Command::Batch(vec![
|
|
Command::Delete(allocation),
|
|
repair(operation_id(34))
|
|
]))
|
|
.is_err()
|
|
);
|
|
assert_eq!(session.project(), &p);
|
|
assert_eq!(language::format(session.project()), source);
|
|
session
|
|
.execute(Command::Batch(vec![
|
|
Command::Delete(allocation),
|
|
repair(operation_id(34)),
|
|
repair(response_id),
|
|
]))
|
|
.unwrap();
|
|
let after = session.project().clone();
|
|
for message in [call, reply] {
|
|
assert!(
|
|
matches!(resolve(&after, message), Ok(OperationResolution::Unresolved { invocation, .. }) if invocation == call)
|
|
);
|
|
let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { values, .. },
|
|
..
|
|
}) = after.elements[&message].shared()
|
|
else {
|
|
panic!()
|
|
};
|
|
assert_eq!(values, &vec![value.clone()]);
|
|
}
|
|
session.undo();
|
|
assert_eq!(session.project(), &p);
|
|
assert_eq!(language::format(session.project()), source);
|
|
session.redo();
|
|
assert_eq!(session.project(), &after);
|
|
}
|
|
fn operation_edit(p: &Project, message: Id, allocation: Option<Id>) -> Element {
|
|
let mut e = p.elements[&message].clone();
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { operation, .. },
|
|
..
|
|
}) = e.shared_mut()
|
|
{
|
|
*operation = Some(OperationUse { allocation });
|
|
}
|
|
e
|
|
}
|
|
#[test]
|
|
fn source7_parameterless_operations_have_exact_protocol_identity_and_deletion_intent() {
|
|
let p = operations_fixture();
|
|
let a = operation_id(43);
|
|
let b = operation_id(44);
|
|
let reset = operation_id(30);
|
|
let status = operation_id(31);
|
|
let q = execute_batch(
|
|
&p,
|
|
&[
|
|
Command::EditElement(a, operation_edit(&p, a, Some(reset))),
|
|
Command::EditElement(b, operation_edit(&p, b, Some(status))),
|
|
],
|
|
)
|
|
.unwrap();
|
|
let OperationResolution::Selected(first) = resolve(&q, a).unwrap() else {
|
|
panic!()
|
|
};
|
|
let OperationResolution::Selected(second) = resolve(&q, b).unwrap() else {
|
|
panic!()
|
|
};
|
|
assert_ne!(first.allocation, second.allocation);
|
|
assert_ne!(first.item, second.item);
|
|
assert_eq!(language::parse(&language::format(&q)).unwrap(), q);
|
|
assert!(execute_batch(&q, &[Command::Delete(reset)]).is_err());
|
|
let mut q = q;
|
|
let another = q
|
|
.add_element(Kind::Interface, Layer::Sa, "Other protocol", None)
|
|
.unwrap();
|
|
q.relate(
|
|
RelationKind::InterfaceProvision,
|
|
"Other provided protocol",
|
|
operation_id(13),
|
|
another,
|
|
);
|
|
q.relate(
|
|
RelationKind::InterfaceRequirement,
|
|
"Other required protocol",
|
|
operation_id(12),
|
|
another,
|
|
);
|
|
let mut retained = q.relations[&reset].clone();
|
|
retained.body.set_endpoints(another, operation_id(20));
|
|
retained.id = Id::new_v4();
|
|
retained.alias = "remaining_operation_allocation".into();
|
|
q.relations.insert(retained.id, retained);
|
|
valid(&q);
|
|
let mut session = Session::new(q.clone());
|
|
session.execute(Command::Delete(reset)).unwrap();
|
|
assert!(matches!(
|
|
resolve(session.project(), a),
|
|
Ok(OperationResolution::Unresolved { .. })
|
|
));
|
|
assert!(matches!(
|
|
resolve(session.project(), b),
|
|
Ok(OperationResolution::Selected(_))
|
|
));
|
|
session.undo();
|
|
assert_eq!(session.project(), &q);
|
|
session.redo();
|
|
assert!(matches!(
|
|
resolve(session.project(), a),
|
|
Ok(OperationResolution::Unresolved { .. })
|
|
));
|
|
let mut wrong = q.elements[&a].clone();
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { operation, .. },
|
|
..
|
|
}) = wrong.shared_mut()
|
|
{
|
|
*operation = Some(OperationUse {
|
|
allocation: Some(first.item),
|
|
});
|
|
}
|
|
assert!(execute_batch(&q, &[Command::EditElement(a, wrong)]).is_err());
|
|
let source = language::format(&q);
|
|
for version in [5, 6] {
|
|
assert!(
|
|
language::parse(&source.replacen("arcadia 7;", &format!("arcadia {version};"), 1))
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|
|
#[test]
|
|
fn source7_ordinary_old_calls_keep_bytes_and_explicit_null_is_not_old_admission() {
|
|
let bytes = include_bytes!(
|
|
"../../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
|
);
|
|
let old: serde_json::Value = serde_json::from_slice(bytes).unwrap();
|
|
let p = operations_fixture();
|
|
let current = serde_json::to_value(&p).unwrap();
|
|
assert_eq!(old["elements"], current["elements"]);
|
|
for version in [5, 6] {
|
|
let mut old = old.clone();
|
|
old["schema_version"] = version.into();
|
|
let current = persistence::decode(&serde_json::to_vec(&old).unwrap()).unwrap();
|
|
assert_eq!(
|
|
serde_json::to_value(¤t).unwrap()["elements"],
|
|
old["elements"]
|
|
);
|
|
let e = &mut old["elements"][operation_id(43).to_string()]["body"]["Sa"]["Shared"]["Occurrence"]
|
|
["body"]["Exchange"];
|
|
e["operation"] = serde_json::Value::Null;
|
|
assert!(persistence::decode(&serde_json::to_vec(&old).unwrap()).is_err());
|
|
}
|
|
}
|
|
#[test]
|
|
fn source7_allocation_removal_requires_independent_channel_repair_and_keeps_values() {
|
|
let mut p = operations_fixture();
|
|
let call = operation_id(43);
|
|
let allocation = operation_id(30);
|
|
let mut field = p
|
|
.make_element(
|
|
Kind::DataField,
|
|
Layer::Sa,
|
|
"Reset reason",
|
|
Some(operation_id(20)),
|
|
)
|
|
.unwrap();
|
|
if let Some(SharedBody::DataField { direction, .. }) = field.shared_mut() {
|
|
*direction = ParameterDirection::In;
|
|
}
|
|
let field_id = field.id;
|
|
p.elements.insert(field.id, field);
|
|
let mut selected = operation_edit(&p, call, Some(allocation));
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { values, .. },
|
|
..
|
|
}) = selected.shared_mut()
|
|
{
|
|
values.push(MessageValue {
|
|
path: vec![],
|
|
field: field_id,
|
|
expression: "unfinished expression (\n preserve".into(),
|
|
});
|
|
}
|
|
p = execute_batch(&p, &[Command::EditElement(call, selected)]).unwrap();
|
|
let before = p.clone();
|
|
assert!(execute_batch(&p, &[Command::Delete(allocation)]).is_err());
|
|
assert_eq!(p, before);
|
|
let mut channel = p.relations[&operation_id(34)].clone();
|
|
if let RelationBody::BoundaryExchange { items, .. } = &mut channel.body {
|
|
items.retain(|id| *id != operation_id(20));
|
|
}
|
|
let after = execute_batch(
|
|
&p,
|
|
&[
|
|
Command::Delete(allocation),
|
|
Command::EditRelation(channel.id, channel),
|
|
],
|
|
)
|
|
.unwrap();
|
|
assert!(matches!(
|
|
resolve(&after, call),
|
|
Ok(OperationResolution::Unresolved { .. })
|
|
));
|
|
let values = |p: &Project| match p.elements[&call].shared().unwrap() {
|
|
SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { values, .. },
|
|
..
|
|
} => values.clone(),
|
|
_ => panic!(),
|
|
};
|
|
assert_eq!(values(&after), values(&p));
|
|
assert!(
|
|
execute_batch(
|
|
&after,
|
|
&[Command::EditElement(
|
|
call,
|
|
operation_edit(&after, call, Some(operation_id(31)))
|
|
)]
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
#[test]
|
|
fn source7_reply_derives_call_signature_and_void_return_channel_still_carries_operation() {
|
|
let mut p = operations_fixture();
|
|
let call = operation_id(43);
|
|
p = execute_batch(
|
|
&p,
|
|
&[Command::EditElement(
|
|
call,
|
|
operation_edit(&p, call, Some(operation_id(30))),
|
|
)],
|
|
)
|
|
.unwrap();
|
|
let reply = occurrence(
|
|
&mut p,
|
|
operation_id(40),
|
|
"Reset reply",
|
|
OccurrenceBody::Exchange {
|
|
exchange: Some(operation_id(34)),
|
|
sender: Some(operation_id(42)),
|
|
receiver: Some(operation_id(41)),
|
|
message_kind: MessageKind::Reply,
|
|
reply_to: Some(call),
|
|
values: vec![],
|
|
operation: None,
|
|
},
|
|
);
|
|
valid(&p);
|
|
let OperationResolution::Selected(resolved) = resolve(&p, reply).unwrap() else {
|
|
panic!()
|
|
};
|
|
assert_eq!(resolved.invocation, call);
|
|
assert_eq!(resolved.provider_port, operation_id(13));
|
|
assert_eq!(resolved.item, operation_id(20));
|
|
assert!(
|
|
execute_batch(
|
|
&p,
|
|
&[Command::EditElement(reply, operation_edit(&p, reply, None))]
|
|
)
|
|
.is_err()
|
|
);
|
|
let mut response = p.relations[&operation_id(34)].clone();
|
|
response.id = Id::new_v4();
|
|
response.alias = "separate_response".into();
|
|
response
|
|
.body
|
|
.set_endpoints(operation_id(13), operation_id(12));
|
|
if let RelationBody::BoundaryExchange { items, .. } = &mut response.body {
|
|
*items = vec![operation_id(21)];
|
|
}
|
|
let response_id = response.id;
|
|
p.relations.insert(response.id, response);
|
|
let mut changed = p.elements[&reply].clone();
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { exchange, .. },
|
|
..
|
|
}) = changed.shared_mut()
|
|
{
|
|
*exchange = Some(response_id);
|
|
}
|
|
assert!(execute_batch(&p, &[Command::EditElement(reply, changed.clone())]).is_err());
|
|
if let RelationBody::BoundaryExchange { items, .. } =
|
|
&mut p.relations.get_mut(&response_id).unwrap().body
|
|
{
|
|
*items = vec![operation_id(20)];
|
|
}
|
|
assert!(execute_batch(&p, &[Command::EditElement(reply, changed)]).is_ok());
|
|
}
|
|
#[test]
|
|
fn source7_transition_preserves_qualified_paths_and_requires_reviewed_operation_omission() {
|
|
let mut f = chain(Layer::Oa);
|
|
let relation = link(
|
|
&f,
|
|
ContextualLinkPurpose::Precedence {
|
|
guard: "ready".into(),
|
|
},
|
|
);
|
|
f.p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
|
|
let mut request = TransitionRequest::new(TransitionDirection::OaToSa, vec![f.outer]);
|
|
request.scope = DependencyScope::Required;
|
|
let plan = plan_transition(&f.p, &request).unwrap();
|
|
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
|
|
let q = execute_batch(&f.p, &plan.commands).unwrap();
|
|
let mapped = |id| plan.changes.iter().find(|c| c.source == id).unwrap().target;
|
|
let RelationBody::ContextualLink { owner, target, .. } =
|
|
&q.relations[&mapped(relation.id)].body
|
|
else {
|
|
panic!()
|
|
};
|
|
assert_eq!(*owner, mapped(f.outer));
|
|
assert_eq!(target.references, vec![mapped(f.r1)]);
|
|
assert_eq!(target.occurrence, mapped(f.b));
|
|
let p = operations_fixture();
|
|
let call = operation_id(43);
|
|
let q = execute_batch(
|
|
&p,
|
|
&[Command::EditElement(
|
|
call,
|
|
operation_edit(&p, call, Some(operation_id(30))),
|
|
)],
|
|
)
|
|
.unwrap();
|
|
let mut request = TransitionRequest::new(TransitionDirection::SaToOa, vec![operation_id(40)]);
|
|
request.scope = DependencyScope::Required;
|
|
let missing = plan_transition(&q, &request).unwrap();
|
|
assert!(
|
|
missing
|
|
.conflicts
|
|
.iter()
|
|
.any(|c| c.source == Some(call) && c.message.contains("omission"))
|
|
);
|
|
request.mappings = vec![
|
|
TransitionMapping {
|
|
source: operation_id(10),
|
|
target: None,
|
|
target_kind: Some(Kind::Actor),
|
|
},
|
|
TransitionMapping {
|
|
source: operation_id(11),
|
|
target: None,
|
|
target_kind: Some(Kind::Entity),
|
|
},
|
|
];
|
|
request.operation_omissions.push(OperationOmission {
|
|
source: call,
|
|
rationale: "Operational conversation abstracts the technical service signature.".into(),
|
|
});
|
|
let plan = plan_transition(&q, &request).unwrap();
|
|
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
|
|
let after = execute_batch(&q, &plan.commands).unwrap();
|
|
let target = plan
|
|
.changes
|
|
.iter()
|
|
.find(|c| c.source == call)
|
|
.unwrap()
|
|
.target;
|
|
assert!(matches!(
|
|
resolve(&after, target),
|
|
Ok(OperationResolution::Ordinary)
|
|
));
|
|
assert!(matches!(
|
|
resolve(&after, call),
|
|
Ok(OperationResolution::Selected(_))
|
|
));
|
|
request
|
|
.operation_omissions
|
|
.push(request.operation_omissions[0].clone());
|
|
assert!(!plan_transition(&q, &request).unwrap().conflicts.is_empty());
|
|
}
|
|
#[test]
|
|
fn source7_assembly_purposes_do_not_invent_precedence_or_global_coverage() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = chain(layer);
|
|
let index = TopologyIndex::new(&f.p);
|
|
let x = index
|
|
.resolve(f.outer, &endpoint(&[], f.x))
|
|
.unwrap()
|
|
.work
|
|
.unwrap();
|
|
let b = index
|
|
.resolve(f.outer, &endpoint(&[f.r1], f.b))
|
|
.unwrap()
|
|
.work
|
|
.unwrap();
|
|
let exchange = Id::new_v4();
|
|
if layer == Layer::Oa {
|
|
f.p.relations.insert(
|
|
exchange,
|
|
Relation {
|
|
id: exchange,
|
|
alias: "assembly_exchange".into(),
|
|
name: "Supply B".into(),
|
|
description: String::new(),
|
|
body: RelationBody::Interaction {
|
|
source: x,
|
|
target: b,
|
|
items: vec![],
|
|
},
|
|
},
|
|
);
|
|
} else {
|
|
let request = FunctionalExchangeRequest {
|
|
id: exchange,
|
|
alias: "assembly_exchange".into(),
|
|
name: "Supply B".into(),
|
|
description: String::new(),
|
|
source: x,
|
|
target: b,
|
|
source_port: PortChoice::Auto,
|
|
target_port: PortChoice::Auto,
|
|
items: vec![],
|
|
};
|
|
f.p = execute_batch(&f.p, &plan_functional_exchange(&f.p, &request).unwrap()).unwrap();
|
|
}
|
|
let relation = link(&f, ContextualLinkPurpose::Exchange { exchange });
|
|
let p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
|
|
let analysis = TopologyIndex::new(&p).analyze(f.outer, AnalysisLimits::default());
|
|
assert!(analysis.complete);
|
|
assert_eq!(analysis.assembly.len(), 1);
|
|
assert!(
|
|
!analysis
|
|
.precedence
|
|
.iter()
|
|
.any(|e| e.relation == Some(relation.id))
|
|
);
|
|
assert!(
|
|
!analysis
|
|
.notices
|
|
.iter()
|
|
.any(|n| n.code == "ambiguous_sequence_branch")
|
|
);
|
|
assert!(
|
|
view_options::behavior_evidence(&p, [f.outer])
|
|
.exchanges
|
|
.contains(&exchange)
|
|
);
|
|
assert!(
|
|
!view_options::behavior_evidence(&p, [f.procedure])
|
|
.exchanges
|
|
.contains(&exchange)
|
|
);
|
|
let mut reverse = relation.clone();
|
|
if let RelationBody::ContextualLink { source, target, .. } = &mut reverse.body {
|
|
std::mem::swap(source, target);
|
|
}
|
|
assert!(execute_batch(&p, &[Command::EditRelation(relation.id, reverse)]).is_err());
|
|
let mut common = relation.clone();
|
|
common.body = RelationBody::ContextualLink {
|
|
owner: f.outer,
|
|
source: endpoint(&[f.r1], f.b),
|
|
target: endpoint(&[f.r2], f.b),
|
|
purpose: ContextualLinkPurpose::CommonWork { work: b },
|
|
};
|
|
let q = execute_batch(&p, &[Command::EditRelation(relation.id, common.clone())]).unwrap();
|
|
let analysis = TopologyIndex::new(&q).analyze(f.outer, AnalysisLimits::default());
|
|
assert_eq!(analysis.assembly.len(), 1);
|
|
assert!(
|
|
!analysis
|
|
.notices
|
|
.iter()
|
|
.any(|n| n.code == "ambiguous_sequence_branch")
|
|
);
|
|
assert!(
|
|
!view_options::behavior_evidence(&q, [f.outer])
|
|
.exchanges
|
|
.contains(&exchange)
|
|
);
|
|
if let RelationBody::ContextualLink { purpose, .. } = &mut common.body {
|
|
*purpose = ContextualLinkPurpose::CommonWork { work: x };
|
|
}
|
|
assert!(execute_batch(&q, &[Command::EditRelation(relation.id, common)]).is_err());
|
|
let removed = execute_batch(&p, &[Command::Delete(exchange)]).unwrap();
|
|
assert!(!removed.relations.contains_key(&relation.id));
|
|
}
|
|
}
|
|
#[test]
|
|
fn source7_source_bundle_migration_is_token_only_and_new_link_stays_with_caller() {
|
|
let f = chain(Layer::Oa);
|
|
let mut bundle = source_project::format_bundle(&f.p);
|
|
let entry = bundle.files.get_mut(&bundle.entry).unwrap();
|
|
*entry = entry
|
|
.replacen("arcadia 7;", "arcadia // preserve this header\r\n 6;", 1)
|
|
.replace('\n', "\r\n");
|
|
let old = bundle.clone();
|
|
let compiled = source_project::compile_bundle(&bundle, Default::default()).unwrap();
|
|
assert_eq!(compiled.decoded.project, f.p);
|
|
assert_eq!(compiled.decoded.migration.as_ref().unwrap().from_schema, 6);
|
|
for (path, source) in &old.files {
|
|
let expected = if path == &old.entry {
|
|
source.replacen(" 6;", " 7;", 1)
|
|
} else {
|
|
source.clone()
|
|
};
|
|
assert_eq!(compiled.bundle.files[path], expected);
|
|
}
|
|
let relation = link(
|
|
&f,
|
|
ContextualLinkPurpose::Precedence {
|
|
guard: "caller only".into(),
|
|
},
|
|
);
|
|
let after = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
|
|
let updated = source_project::update_bundle(&compiled, &after, Default::default()).unwrap();
|
|
assert_eq!(
|
|
updated.index.declarations[&relation.id].path,
|
|
updated.index.declarations[&f.outer].path
|
|
);
|
|
let procedure_file = &compiled.index.declarations[&f.procedure].path;
|
|
assert_eq!(
|
|
compiled.bundle.files[procedure_file],
|
|
updated.bundle.files[procedure_file]
|
|
);
|
|
assert_eq!(
|
|
source_project::compile_bundle(&updated.bundle, Default::default())
|
|
.unwrap()
|
|
.decoded
|
|
.project,
|
|
after
|
|
);
|
|
}
|
|
#[test]
|
|
fn source7_semantic_facets_retain_typed_context_and_operation_references() {
|
|
let f = chain(Layer::Oa);
|
|
let relation = link(
|
|
&f,
|
|
ContextualLinkPurpose::Precedence {
|
|
guard: "uuid-shaped prose remains prose".into(),
|
|
},
|
|
);
|
|
let p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
|
|
let facet = capture_facet(
|
|
&p,
|
|
relation.id,
|
|
relation.id,
|
|
&FacetSelector::Relation(RelationFacet::ContextTarget),
|
|
)
|
|
.unwrap();
|
|
let mut baseline = SemanticBaseline {
|
|
facets: vec![facet],
|
|
};
|
|
let new = Id::new_v4();
|
|
visit_semantic_references(&mut baseline, |id| {
|
|
if *id == f.r1 {
|
|
*id = new
|
|
}
|
|
});
|
|
let FacetReview::Relation(RelationReview::ContextTarget(review)) = &baseline.facets[0] else {
|
|
panic!()
|
|
};
|
|
let Observed::Value(target) = &review.oa else {
|
|
panic!()
|
|
};
|
|
assert_eq!(target.references, vec![new]);
|
|
assert_eq!(target.occurrence, f.b);
|
|
let source = capture_facet(
|
|
&p,
|
|
relation.id,
|
|
relation.id,
|
|
&FacetSelector::Relation(RelationFacet::Source),
|
|
);
|
|
assert!(
|
|
source
|
|
.unwrap_err()
|
|
.contains("must apply on at least one side")
|
|
);
|
|
let p = operations_fixture();
|
|
let call = operation_id(43);
|
|
let facet = capture_facet(
|
|
&p,
|
|
Id::nil(),
|
|
call,
|
|
&FacetSelector::Occurrence(OccurrenceFacet::Operation),
|
|
)
|
|
.unwrap();
|
|
assert!(matches!(
|
|
facet,
|
|
FacetReview::Occurrence(OccurrenceReview::Operation(Reviewed {
|
|
oa: Observed::NotApplicable,
|
|
sa: Observed::Value(None)
|
|
}))
|
|
));
|
|
let q = execute_batch(
|
|
&p,
|
|
&[Command::EditElement(
|
|
call,
|
|
operation_edit(&p, call, Some(operation_id(30))),
|
|
)],
|
|
)
|
|
.unwrap();
|
|
let facet = capture_facet(
|
|
&q,
|
|
Id::nil(),
|
|
call,
|
|
&FacetSelector::Occurrence(OccurrenceFacet::Operation),
|
|
)
|
|
.unwrap();
|
|
let mut baseline = SemanticBaseline {
|
|
facets: vec![facet],
|
|
};
|
|
visit_semantic_references(&mut baseline, |id| {
|
|
if *id == operation_id(30) {
|
|
*id = new
|
|
}
|
|
});
|
|
let FacetReview::Occurrence(OccurrenceReview::Operation(review)) = &baseline.facets[0] else {
|
|
panic!()
|
|
};
|
|
assert_eq!(
|
|
review.sa,
|
|
Observed::Value(Some(OperationUse {
|
|
allocation: Some(new)
|
|
}))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn source7_selected_operation_requires_channel_lifelines_and_proven_direction() {
|
|
let p = operations_fixture();
|
|
let call = operation_id(43);
|
|
for missing in ["channel", "lifeline", "direction"] {
|
|
let mut candidate = p.clone();
|
|
let mut message = operation_edit(&candidate, call, Some(operation_id(30)));
|
|
let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange {
|
|
exchange, sender, ..
|
|
},
|
|
..
|
|
}) = message.shared_mut()
|
|
else {
|
|
panic!()
|
|
};
|
|
match missing {
|
|
"channel" => *exchange = None,
|
|
"lifeline" => *sender = None,
|
|
_ => {
|
|
let Some(SharedBody::Lifeline { represented }) = candidate
|
|
.elements
|
|
.get_mut(&operation_id(41))
|
|
.unwrap()
|
|
.shared_mut()
|
|
else {
|
|
panic!()
|
|
};
|
|
*represented = None;
|
|
}
|
|
}
|
|
candidate.elements.insert(call, message.clone());
|
|
let code = resolve(&candidate, call).unwrap_err().code;
|
|
assert_eq!(
|
|
code,
|
|
match missing {
|
|
"channel" => "operation_channel",
|
|
"lifeline" => "operation_lifelines",
|
|
_ => "operation_direction",
|
|
}
|
|
);
|
|
assert!(execute_batch(&p, &[Command::Replace(candidate.clone())]).is_err());
|
|
let unresolved = operation_edit(&candidate, call, None);
|
|
candidate.elements.insert(call, unresolved);
|
|
valid(&candidate);
|
|
assert!(matches!(
|
|
resolve(&candidate, call),
|
|
Ok(OperationResolution::Unresolved { .. })
|
|
));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn source7_context_resource_limits_are_per_request_and_count_authored_paths() {
|
|
let mut f = chain(Layer::Oa);
|
|
for n in 0..100 {
|
|
occurrence(
|
|
&mut f.p,
|
|
f.outer,
|
|
&format!("Reuse {n}"),
|
|
OccurrenceBody::SequenceReference {
|
|
sequence: Some(f.procedure),
|
|
},
|
|
);
|
|
}
|
|
let relation = link(
|
|
&f,
|
|
ContextualLinkPurpose::Precedence {
|
|
guard: String::new(),
|
|
},
|
|
);
|
|
f.p = execute_batch(&f.p, &[Command::CreateRelation(relation)]).unwrap();
|
|
let old_steps: usize =
|
|
f.p.elements
|
|
.values()
|
|
.map(|e| e.steps().map_or(0, Vec::len))
|
|
.sum();
|
|
let mut limits = language::ParseLimits::UNLIMITED;
|
|
limits.max_step_references = old_steps;
|
|
assert!(limits.check_model(&f.p).is_err());
|
|
limits.max_step_references += 1;
|
|
assert!(limits.check_model(&f.p).is_ok());
|
|
let cold = TopologyIndex::with_cache_capacity(&f.p, 0);
|
|
let hot = TopologyIndex::with_cache_capacity(&f.p, 1);
|
|
let full = cold.analyze(
|
|
f.outer,
|
|
AnalysisLimits {
|
|
nodes: 1000,
|
|
edges: 2000,
|
|
},
|
|
);
|
|
assert!(full.complete);
|
|
assert_eq!(full.nodes.len(), 409);
|
|
assert!(
|
|
!hot.analyze(
|
|
f.outer,
|
|
AnalysisLimits {
|
|
nodes: 300,
|
|
edges: 2000
|
|
}
|
|
)
|
|
.complete
|
|
);
|
|
let again = hot.analyze(
|
|
f.outer,
|
|
AnalysisLimits {
|
|
nodes: 1000,
|
|
edges: 2000,
|
|
},
|
|
);
|
|
assert!(again.complete);
|
|
assert_eq!(again.nodes, full.nodes);
|
|
assert_eq!(again.precedence, full.precedence);
|
|
assert_eq!(again.notices, full.notices);
|
|
let bad = endpoint(&vec![f.r1; 65], f.b);
|
|
assert_eq!(
|
|
hot.resolve(f.outer, &bad).unwrap_err().code,
|
|
"context_depth"
|
|
);
|
|
assert_eq!(hot.resolve_scope(f.outer, &[f.r1]).unwrap(), f.procedure);
|
|
}
|