#![allow(clippy::unwrap_used)]
use rayon::prelude::*;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use syster::project::file_loader;
fn get_examples_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/sysml-examples")
}
fn collect_sysml_files(dir: &Path, files: &mut Vec<PathBuf>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_sysml_files(&path, files);
} else if path.extension().is_some_and(|ext| ext == "sysml") {
files.push(path);
}
}
}
}
#[test]
fn test_sysml_examples_parsing() {
let examples_dir = get_examples_dir();
if !examples_dir.exists() {
eprintln!("⏭️ Skipping: sysml-examples directory not found at {examples_dir:?}");
eprintln!(" To run these tests, execute:");
eprintln!(
" git clone --depth 1 https://github.com/Systems-Modeling/SysML-v2-Release.git /tmp/sysml"
);
eprintln!(" cp -r /tmp/sysml/sysml/src/examples crates/syster-base/tests/sysml-examples");
return;
}
let mut files = Vec::new();
collect_sysml_files(&examples_dir, &mut files);
files.sort();
if files.is_empty() {
eprintln!("⚠️ No .sysml files found in {examples_dir:?}");
return;
}
let passed = Mutex::new(Vec::new());
let failed: Mutex<HashMap<String, Vec<String>>> = Mutex::new(HashMap::new());
files.par_iter().for_each(|file_path| {
let relative = file_path
.strip_prefix(&examples_dir)
.unwrap_or(file_path)
.display()
.to_string();
let content = match std::fs::read_to_string(file_path) {
Ok(c) => c,
Err(e) => {
failed
.lock()
.unwrap()
.entry(format!("IO Error: {e}"))
.or_default()
.push(relative);
return;
}
};
let parse_result = file_loader::parse_with_result(&content, file_path);
if parse_result.content.is_some() && parse_result.errors.is_empty() {
passed.lock().unwrap().push(relative);
} else {
let error_msg = parse_result
.errors
.first()
.map(|e| {
if let Some(pos) = e.message.find("expected ") {
let rest = &e.message[pos..];
if let Some(end) = rest.find('\n') {
rest[..end].to_string()
} else {
rest.to_string()
}
} else {
e.message.clone()
}
})
.unwrap_or_else(|| "Unknown error".to_string());
failed
.lock()
.unwrap()
.entry(error_msg)
.or_default()
.push(relative);
}
});
let passed = passed.into_inner().unwrap();
let failed = failed.into_inner().unwrap();
let total = files.len();
let pass_count = passed.len();
let fail_count = total - pass_count;
let pass_rate = (pass_count as f64 / total as f64) * 100.0;
eprintln!("\n╔════════════════════════════════════════════════════════════════╗");
eprintln!("║ SysML v2 Examples Parsing Summary ║");
eprintln!("╠════════════════════════════════════════════════════════════════╣");
eprintln!("║ Total files: {total:>4} ║");
eprintln!(
"║ Passed: {pass_count:>4} ({pass_rate:>5.1}%) ║"
);
eprintln!(
"║ Failed: {:>4} ({:>5.1}%) ║",
fail_count,
100.0 - pass_rate
);
eprintln!("╚════════════════════════════════════════════════════════════════╝");
if !failed.is_empty() {
eprintln!("\n📋 Failures by error pattern:");
let mut error_counts: Vec<_> = failed.iter().collect();
error_counts.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
for (error, files) in error_counts {
eprintln!("\n ❌ {} ({} files)", error, files.len());
for f in files.iter().take(3) {
eprintln!(" - {f}");
}
if files.len() > 3 {
eprintln!(" ... and {} more", files.len() - 3);
}
}
}
if !passed.is_empty() {
eprintln!("\n✅ Passing files ({}):", passed.len());
for f in &passed {
eprintln!(" - {f}");
}
}
eprintln!();
}
#[test]
fn test_no_regressions() {
let examples_dir = get_examples_dir();
if !examples_dir.exists() {
return; }
let must_pass = [
"Simple Tests/ImportTest.sysml",
"Simple Tests/AliasTest.sysml",
"Simple Tests/EnumerationTest.sysml",
"Simple Tests/MultiplicityTest.sysml",
"Simple Tests/DependencyTest.sysml",
"Simple Tests/DefaultValueTest.sysml",
"Simple Tests/ConstraintTest.sysml",
"Import Tests/AliasImport.sysml",
"Import Tests/CircularImport.sysml",
"Import Tests/PrivateImportTest.sysml",
"Import Tests/QualifiedNameImportTest.sysml",
"Comment Examples/Comments.sysml",
"Simple Tests/StructuredControlTest.sysml",
];
let mut regressions = Vec::new();
for relative_path in must_pass {
let file_path = examples_dir.join(relative_path);
if !file_path.exists() {
continue; }
let content = match std::fs::read_to_string(&file_path) {
Ok(c) => c,
Err(_) => continue,
};
let parse_result = file_loader::parse_with_result(&content, &file_path);
if parse_result.content.is_none() || !parse_result.errors.is_empty() {
let error = parse_result
.errors
.first()
.map(|e| e.message.clone())
.unwrap_or_else(|| "Unknown error".to_string());
regressions.push(format!("{relative_path}: {error}"));
}
}
if !regressions.is_empty() {
panic!(
"🚨 REGRESSION: {} previously-passing files now fail:\n - {}",
regressions.len(),
regressions.join("\n - ")
);
}
}
macro_rules! example_test {
($name:ident, $path:expr) => {
#[test]
fn $name() {
let examples_dir = get_examples_dir();
let file_path = examples_dir.join($path);
if !file_path.exists() {
eprintln!("Skipping: file not found at {:?}", file_path);
return;
}
let content = std::fs::read_to_string(&file_path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", $path, e));
let parse_result = file_loader::parse_with_result(&content, &file_path);
assert!(
parse_result.content.is_some() && parse_result.errors.is_empty(),
"Failed to parse {}:\n{}",
$path,
parse_result
.errors
.iter()
.map(|e| format!(" Line {}: {}", e.position.line, e.message))
.collect::<Vec<_>>()
.join("\n")
);
}
};
}
example_test!(
example_analysis_annotation,
"Analysis Examples/AnalysisAnnotation.sysml"
);
example_test!(
example_turbojet_stage_analysis,
"Analysis Examples/Turbojet Stage Analysis.sysml"
);
example_test!(
example_vehicle_analysis_demo,
"Analysis Examples/Vehicle Analysis Demo.sysml"
);
example_test!(
example_ahf_sequences,
"Arrowhead Framework Example/AHFSequences.sysml"
);
example_test!(
example_ahf_norway_topics,
"Arrowhead Framework Example/AHFNorwayTopics.sysml"
);
example_test!(
example_product_selection_unowned_ends,
"Association Examples/ProductSelection_UnownedEnds.sysml"
);
example_test!(example_picture_taking, "Camera Example/PictureTaking.sysml");
example_test!(
example_cause_and_effect,
"Cause and Effect Examples/CauseAndEffectExample.sysml"
);
example_test!(
example_flashlight,
"Flashlight Example/Flashlight Example.sysml"
);
example_test!(
example_external_shape_ref,
"Geometry Examples/ExternalShapeRefExample.sysml"
);
example_test!(
example_vehicle_geometry_coords,
"Geometry Examples/VehicleGeometryAndCoordinateFrames.sysml"
);
example_test!(
example_server_sequence_outside_realization_3,
"Interaction Sequencing Examples/ServerSequenceOutsideRealization-3.sysml"
);
example_test!(
example_server_sequence_realization_3,
"Interaction Sequencing Examples/ServerSequenceRealization-3.sysml"
);
example_test!(example_mass_rollup, "Mass Roll-up Example/MassRollup.sysml");
example_test!(
example_issue_metadata,
"Metadata Examples/IssueMetadataExample.sysml"
);
example_test!(
example_requirement_metadata,
"Metadata Examples/RequirementMetadataExample.sysml"
);
example_test!(
example_risk_metadata,
"Metadata Examples/RiskMetadataExample.sysml"
);
example_test!(
example_verification_metadata,
"Metadata Examples/VerificationMetadataExample.sysml"
);
example_test!(
example_requirement_derivation,
"Requirements Examples/RequirementDerivationExample.sysml"
);
example_test!(example_room_model, "Room Model/RoomModel.sysml");
example_test!(example_action_test, "Simple Tests/ActionTest.sysml");
example_test!(example_allocation_test, "Simple Tests/AllocationTest.sysml");
example_test!(example_analysis_test, "Simple Tests/AnalysisTest.sysml");
example_test!(example_assignment_test, "Simple Tests/AssignmentTest.sysml");
example_test!(example_comment_test, "Simple Tests/CommentTest.sysml");
example_test!(example_connection_test, "Simple Tests/ConnectionTest.sysml");
example_test!(
example_control_node_test,
"Simple Tests/ControlNodeTest.sysml"
);
example_test!(example_decision_test, "Simple Tests/DecisionTest.sysml");
example_test!(
example_feature_path_test,
"Simple Tests/FeaturePathTest.sysml"
);
example_test!(example_part_test, "Simple Tests/PartTest.sysml");
example_test!(
example_requirement_test,
"Simple Tests/RequirementTest.sysml"
);
example_test!(example_state_test, "Simple Tests/StateTest.sysml");
example_test!(
example_structured_control_test,
"Simple Tests/StructuredControlTest.sysml"
);
example_test!(
example_textual_representation_test,
"Simple Tests/TextualRepresentationTest.sysml"
);
example_test!(example_use_case_test, "Simple Tests/UseCaseTest.sysml");
example_test!(
example_variability_test,
"Simple Tests/VariabilityTest.sysml"
);
example_test!(
example_verification_test,
"Simple Tests/VerificationTest.sysml"
);
example_test!(example_view_test, "Simple Tests/ViewTest.sysml");
example_test!(
example_cart_sample,
"State Space Representation Examples/CartSample.sysml"
);
example_test!(
example_time_varying_attribute,
"Timeslice and Snapshot Examples/TimeVaryingAttribute.sysml"
);
example_test!(
example_vehicle_variability_model,
"Variability Examples/VehicleVariabilityModel.sysml"
);
example_test!(
example_vehicle_individuals,
"Vehicle Example/VehicleIndividuals.sysml"
);
example_test!(
example_sysml_spec_annex_a,
"Vehicle Example/SysML v2 Spec Annex A SimpleVehicleModel.sysml"
);
use syster::ide::AnalysisHost;
use syster::project::StdLibLoader;
fn get_stdlib_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sysml.library")
}
fn create_host_with_stdlib() -> AnalysisHost {
let mut host = AnalysisHost::new();
let stdlib_dir = get_stdlib_dir();
if stdlib_dir.exists() {
let mut loader = StdLibLoader::with_path(stdlib_dir);
let _ = loader.ensure_loaded_into_host(&mut host);
}
host
}
fn load_example_dir(host: &mut AnalysisHost, dir: &Path) {
if !dir.exists() {
return;
}
for entry in walkdir::WalkDir::new(dir).into_iter().flatten() {
let path = entry.path();
if path
.extension()
.is_some_and(|e| e == "sysml" || e == "kerml")
{
if let Ok(content) = std::fs::read_to_string(path) {
host.set_file_content(&path.to_string_lossy(), &content);
}
}
}
host.rebuild_index();
}
fn get_errors_for_dir(host: &AnalysisHost, dir: &Path) -> Vec<(String, syster::hir::Diagnostic)> {
host.all_errors()
.into_iter()
.filter(|(path, _)| Path::new(path).starts_with(dir))
.collect()
}
macro_rules! semantic_example_test {
($name:ident, $dir:expr) => {
#[test]
fn $name() {
let examples_dir = get_examples_dir();
let target_dir = examples_dir.join($dir);
if !target_dir.exists() {
eprintln!("⏭️ Skipping {}: not found", $dir);
return;
}
let mut host = create_host_with_stdlib();
load_example_dir(&mut host, &target_dir);
let errors = get_errors_for_dir(&host, &target_dir);
let file_count = host
.file_id_map()
.keys()
.filter(|p| Path::new(*p).starts_with(&target_dir))
.count();
if errors.is_empty() {
eprintln!("✓ {}: {} files, 0 semantic errors", $dir, file_count);
} else {
for (path, diag) in &errors {
let rel = Path::new(path)
.file_name()
.unwrap_or_default()
.to_string_lossy();
eprintln!(
" {}:{}:{}: {}",
rel,
diag.start_line + 1,
diag.start_col + 1,
diag.message
);
}
panic!("{}: expected 0 errors, found {}", $dir, errors.len());
}
}
};
}
semantic_example_test!(
test_arrowhead_framework_semantic,
"Arrowhead Framework Example"
);
semantic_example_test!(test_simple_vehicle_semantic, "Simple Vehicle Example");
semantic_example_test!(test_vehicle_example_semantic, "Vehicle Example");
semantic_example_test!(test_analysis_examples_semantic, "Analysis Examples");
semantic_example_test!(test_metadata_examples_semantic, "Metadata Examples");