use sysml_v2_parser::ast::*;
use sysml_v2_parser::parse;
#[test]
fn test_objective_parses_named_typed_requirement_usage() {
let input = "package P { use case def U { objective missionObjective : MaximizeObjective; } }";
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let use_case = match &elements[0].value {
PackageBodyElement::UseCaseDef(uc) => &uc.value,
_ => panic!("expected UseCaseDef"),
};
let body_elements = match &use_case.body {
sysml_v2_parser::ast::UseCaseDefBody::Brace { elements } => elements,
_ => panic!("expected use case brace body"),
};
let objective = body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::UseCaseDefBodyElement::Objective(o) => Some(&o.value),
_ => None,
})
.expect("objective should be present");
assert_eq!(objective.requirement.value.name, "missionObjective");
assert_eq!(
objective.requirement.value.type_name.as_deref(),
Some("MaximizeObjective")
);
assert!(matches!(
objective.requirement.value.body,
sysml_v2_parser::ast::RequirementDefBody::Semicolon
));
}
#[test]
fn test_objective_body_preserves_structured_requirement_members() {
let input = "package P { use case def U { objective verificationObjective { doc /* verify behavior */ require constraint { true; } } } }";
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let use_case = match &elements[0].value {
PackageBodyElement::UseCaseDef(uc) => &uc.value,
_ => panic!("expected UseCaseDef"),
};
let body_elements = match &use_case.body {
sysml_v2_parser::ast::UseCaseDefBody::Brace { elements } => elements,
_ => panic!("expected use case brace body"),
};
let objective = body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::UseCaseDefBodyElement::Objective(o) => Some(&o.value),
_ => None,
})
.expect("objective should be present");
let req_body_elements = match &objective.requirement.value.body {
sysml_v2_parser::ast::RequirementDefBody::Brace { elements } => elements,
_ => panic!("expected objective requirement brace body"),
};
assert!(req_body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::Doc(_)
)));
assert!(req_body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::RequireConstraint(_)
)));
}
#[test]
fn test_objective_typed_semicolon_uses_default_name() {
let input = "package P { use case def U { objective : MaximizeObjective; } }";
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let use_case = match &elements[0].value {
PackageBodyElement::UseCaseDef(uc) => &uc.value,
_ => panic!("expected UseCaseDef"),
};
let body_elements = match &use_case.body {
sysml_v2_parser::ast::UseCaseDefBody::Brace { elements } => elements,
_ => panic!("expected use case brace body"),
};
let objective = body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::UseCaseDefBodyElement::Objective(o) => Some(&o.value),
_ => None,
})
.expect("objective should be present");
assert_eq!(objective.requirement.value.name, "objective");
assert_eq!(
objective.requirement.value.type_name.as_deref(),
Some("MaximizeObjective")
);
}
#[test]
fn test_objective_preserves_visibility_prefix() {
let input = "package P { use case def U { private objective O { } } }";
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let use_case = match &elements[0].value {
PackageBodyElement::UseCaseDef(uc) => &uc.value,
_ => panic!("expected UseCaseDef"),
};
let body_elements = match &use_case.body {
sysml_v2_parser::ast::UseCaseDefBody::Brace { elements } => elements,
_ => panic!("expected use case brace body"),
};
let objective = body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::UseCaseDefBodyElement::Objective(o) => Some(&o.value),
_ => None,
})
.expect("objective should be present");
assert!(matches!(
objective.visibility,
Some(sysml_v2_parser::ast::Visibility::Private)
));
}
#[test]
fn test_objective_body_parses_verify_shorthand_and_explicit_requirement() {
let input = "package P { use case def U { objective O { verify vehicleMassRequirement; verify requirement vehicleMassRequirement : VehicleMassRequirement; } } }";
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let use_case = match &elements[0].value {
PackageBodyElement::UseCaseDef(uc) => &uc.value,
_ => panic!("expected UseCaseDef"),
};
let body_elements = match &use_case.body {
sysml_v2_parser::ast::UseCaseDefBody::Brace { elements } => elements,
_ => panic!("expected use case brace body"),
};
let objective = body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::UseCaseDefBodyElement::Objective(o) => Some(&o.value),
_ => None,
})
.expect("objective should be present");
let req_body_elements = match &objective.requirement.value.body {
sysml_v2_parser::ast::RequirementDefBody::Brace { elements } => elements,
_ => panic!("expected objective requirement brace body"),
};
let shorthand = req_body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::RequirementDefBodyElement::VerifyRequirement(v)
if !v.value.explicit_requirement_keyword =>
{
Some(&v.value)
}
_ => None,
})
.expect("shorthand verify should be present");
assert_eq!(shorthand.target.as_deref(), Some("vehicleMassRequirement"));
let explicit = req_body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::RequirementDefBodyElement::VerifyRequirement(v)
if v.value.explicit_requirement_keyword =>
{
Some(&v.value)
}
_ => None,
})
.expect("explicit verify requirement should be present");
let explicit_req = explicit
.requirement
.as_ref()
.expect("explicit form should include parsed requirement usage");
assert_eq!(explicit_req.value.name, "vehicleMassRequirement");
assert_eq!(
explicit_req.value.type_name.as_deref(),
Some("VehicleMassRequirement")
);
}
#[test]
fn test_requirement_body_keeps_structured_attributes_and_later_require_constraint() {
let input = "package P {\nrequirement def R {\nsubject vehicle : Vehicle;\nattribute massActual: MassValue;\nattribute measuredMass = 42;\nrequire constraint { }\n}\n}";
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let req = elements
.iter()
.find_map(|e| match &e.value {
PackageBodyElement::RequirementDef(r) => Some(&r.value),
_ => None,
})
.expect("requirement def should be present");
let body_elements = match &req.body {
sysml_v2_parser::ast::RequirementDefBody::Brace { elements } => elements,
_ => panic!("expected requirement brace body"),
};
assert!(
body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::SubjectDecl(_)
)),
"subject should be parsed in requirement body"
);
assert!(
body_elements.iter().any(|e| matches!(
&e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::AttributeDef(a)
if a.value.typing.is_some()
)),
"typed attribute members in requirement definitions should be attribute definitions"
);
assert!(
body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::AttributeUsage(_)
)),
"value-based attribute members should be preserved as structured attribute usages"
);
assert!(
body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::RequireConstraint(_)
)),
"require constraint should be preserved after structured attribute members"
);
}
#[test]
fn test_parse_requirement_body_supports_attribute_def_and_usage_forms() {
let input = "package P {\nrequirement def R {\nattribute def targetMass: MassValue;\nattribute actualMass = measuredMass;\n}\n}";
let result = parse(input).expect("requirement body attributes should parse");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let req = elements
.iter()
.find_map(|e| match &e.value {
PackageBodyElement::RequirementDef(r) => Some(&r.value),
_ => None,
})
.expect("requirement def should be present");
let body_elements = match &req.body {
sysml_v2_parser::ast::RequirementDefBody::Brace { elements } => elements,
_ => panic!("expected requirement brace body"),
};
assert!(
body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::AttributeDef(_)
)),
"attribute def form should be preserved"
);
assert!(
body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::AttributeUsage(_)
)),
"attribute usage form should be preserved"
);
}
#[test]
fn test_parse_part_usage_body_satisfy_shorthand() {
let input =
"package P {\npart def Home {\npart livingRoom: Room {\nsatisfy heatSuff5;\n}\n}\n}";
let result = parse(input).expect("satisfy shorthand in part usage should parse");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let home = elements
.iter()
.find_map(|e| match &e.value {
PackageBodyElement::PartDef(p)
if p.value.identification.name.as_deref() == Some("Home") =>
{
Some(&p.value)
}
_ => None,
})
.expect("Home part def should be present");
let home_body = match &home.body {
sysml_v2_parser::ast::PartDefBody::Brace { elements } => elements,
_ => panic!("expected Home part def body"),
};
let living_room = home_body
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::PartDefBodyElement::PartUsage(p)
if p.value.name == "livingRoom" =>
{
Some(&p.value)
}
_ => None,
})
.expect("livingRoom part usage should be present");
let living_room_body = match &living_room.body {
sysml_v2_parser::ast::PartUsageBody::Brace { elements } => elements,
_ => panic!("expected livingRoom part usage body"),
};
assert!(
living_room_body.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::PartUsageBodyElement::Satisfy(_)
)),
"satisfy shorthand should be preserved in part usage body"
);
}
#[test]
fn test_parse_require_constraint_keeps_inner_members() {
let input = "package P {\nrequirement def R {\nrequire constraint {\ndoc /* requirement logic */\nin x : Real;\nout y : Real;\nx >= y;\n}\n}\n}";
let result = parse(input).expect("require constraint body should parse");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let req = elements
.iter()
.find_map(|e| match &e.value {
PackageBodyElement::RequirementDef(r) => Some(&r.value),
_ => None,
})
.expect("requirement def should be present");
let body_elements = match &req.body {
sysml_v2_parser::ast::RequirementDefBody::Brace { elements } => elements,
_ => panic!("expected requirement brace body"),
};
let require_constraint = body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::RequirementDefBodyElement::RequireConstraint(c) => Some(&c.value),
_ => None,
})
.expect("require constraint should be present");
let constraint_elements = match &require_constraint.body {
sysml_v2_parser::ast::RequireConstraintBody::Brace { elements } => elements,
_ => panic!("expected structured require constraint body"),
};
assert!(
constraint_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::ConstraintDefBodyElement::Doc(_)
)),
"doc should be preserved inside require constraint"
);
assert!(
constraint_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::ConstraintDefBodyElement::InOutDecl(_)
)),
"in/out declarations should be preserved inside require constraint"
);
assert!(
constraint_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::ConstraintDefBodyElement::Expression(_)
)),
"expressions should be preserved inside require constraint"
);
}
#[test]
fn test_parse_requirement_subject_shorthand_without_name() {
let input = "package P {\nrequirement def R {\nsubject: Laptop;\nrequire constraint { }\n}\n}";
let result = parse(input).expect("subject shorthand should parse");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let elements = match &pkg.body {
PackageBody::Brace { elements } => elements,
_ => panic!("expected brace body"),
};
let req = elements
.iter()
.find_map(|e| match &e.value {
PackageBodyElement::RequirementDef(r) => Some(&r.value),
_ => None,
})
.expect("requirement def should be present");
let body_elements = match &req.body {
sysml_v2_parser::ast::RequirementDefBody::Brace { elements } => elements,
_ => panic!("expected requirement brace body"),
};
let subject = body_elements
.iter()
.find_map(|e| match &e.value {
sysml_v2_parser::ast::RequirementDefBodyElement::SubjectDecl(s) => Some(&s.value),
_ => None,
})
.expect("subject decl should be present");
assert_eq!(subject.name, "subject");
assert_eq!(subject.type_name, "Laptop");
assert!(
body_elements.iter().any(|e| matches!(
e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::RequireConstraint(_)
)),
"later requirement members should still parse after subject shorthand"
);
}
#[test]
fn test_requirement_usage_accepts_subsets_keyword_alias() {
let input = r#"package P {
requirement VehicleReq; subsets BaseReq;
}"#;
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => p,
other => panic!("expected package, got {:?}", other),
};
let elements = match &pkg.value.body {
PackageBody::Brace { elements } => elements,
other => panic!("expected brace body, got {:?}", other),
};
let req = match &elements[0].value {
PackageBodyElement::RequirementUsage(r) => r,
other => panic!("expected requirement usage, got {:?}", other),
};
assert_eq!(req.value.subsets.as_deref(), Some("BaseReq"));
}
#[test]
fn test_requirement_usage_accepts_multiple_subsets_clauses() {
let input = r#"package P {
requirement VehicleReq; subsets BaseReq :> LatestReq;
}"#;
let result = parse(input).expect("parse should succeed");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => p,
other => panic!("expected package, got {:?}", other),
};
let elements = match &pkg.value.body {
PackageBody::Brace { elements } => elements,
other => panic!("expected brace body, got {:?}", other),
};
let req = match &elements[0].value {
PackageBodyElement::RequirementUsage(r) => r,
other => panic!("expected requirement usage, got {:?}", other),
};
assert_eq!(req.value.subsets.as_deref(), Some("LatestReq"));
}
#[test]
fn test_requirement_body_attribute_typed_with_value_and_redefine_forms() {
let input = r#"package P {
requirement def R {
attribute targetMass : Real = (a - (b - c));
attribute measuredMass :>> Vehicle::mass = ((a - b) - c);
}
}"#;
let result = parse(input).expect("requirement attributes should parse");
let pkg = match &result.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let PackageBody::Brace { elements } = &pkg.body else {
panic!("expected package body");
};
let req = elements
.iter()
.find_map(|e| match &e.value {
PackageBodyElement::RequirementDef(r) => Some(&r.value),
_ => None,
})
.expect("expected requirement definition");
let sysml_v2_parser::ast::RequirementDefBody::Brace { elements } = &req.body else {
panic!("expected requirement body");
};
assert!(elements.iter().any(|e| matches!(
&e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::AttributeDef(a)
if a.value.typing.is_some()
)));
assert!(elements.iter().any(|e| matches!(
&e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::AttributeUsage(a)
if a.value.redefines.is_some()
)));
}
#[test]
fn test_requirement_local_typed_real_attribute_is_clean_in_diagnostics() {
let input = r#"package P {
requirement def VehicleMassRequirement {
attribute targetMass : Real = (a - (b - c));
require constraint {
in actualMass : Real;
actualMass >= targetMass;
}
}
}"#;
let result = sysml_v2_parser::parse_with_diagnostics(input);
assert!(
result.errors.is_empty(),
"typed requirement-local Real attribute should not produce recovery diagnostics: {:?}",
result.errors
);
let pkg = match &result.root.elements[0].value {
RootElement::Package(p) => &p.value,
_ => panic!("expected package"),
};
let PackageBody::Brace { elements } = &pkg.body else {
panic!("expected package body");
};
let req = elements
.iter()
.find_map(|e| match &e.value {
PackageBodyElement::RequirementDef(r) => Some(&r.value),
_ => None,
})
.expect("expected requirement definition");
let sysml_v2_parser::ast::RequirementDefBody::Brace { elements } = &req.body else {
panic!("expected requirement body");
};
assert!(elements.iter().any(|e| matches!(
&e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::AttributeDef(a)
if a.value.typing.is_some()
)));
assert!(elements.iter().any(|e| matches!(
&e.value,
sysml_v2_parser::ast::RequirementDefBodyElement::RequireConstraint(_)
)));
}