679 lines
24 KiB
Rust
679 lines
24 KiB
Rust
//! Current-schema core-only native deletion/alias oracle. No desktop code or actual-derived expectation.
|
|
//! renamed BASE_ENTRY ACTUAL_ENTRY TARGET_UUID NEW_ALIAS | removed BASE_ENTRY ACTUAL_ENTRY TARGET_UUID
|
|
//! same BASE_ENTRY ACTUAL_ENTRY | self-check | generate NEW_DIR
|
|
use oa_core::{source_project::*, *};
|
|
use std::{collections::BTreeMap, fs, path::Path};
|
|
|
|
fn compile_exact(raw: &SourceBundle) -> CompiledSourceProject {
|
|
let result = compile_bundle(raw, ProjectLimits::default()).expect("source admission");
|
|
assert_eq!(
|
|
&result.bundle, raw,
|
|
"source migration/normalization is not permitted"
|
|
);
|
|
assert_eq!(
|
|
SCHEMA_VERSION, 7,
|
|
"this oracle is admitted only for current schema 7 core"
|
|
);
|
|
assert_eq!(
|
|
result.decoded.project.schema_version, 7,
|
|
"schema 7 required"
|
|
);
|
|
validate_candidate(&result.decoded.project).expect("model admission");
|
|
result
|
|
}
|
|
fn load(entry: &Path) -> CompiledSourceProject {
|
|
try_load(entry).unwrap_or_else(|error| panic!("{}: {error}", entry.display()))
|
|
}
|
|
fn try_load(entry: &Path) -> Result<CompiledSourceProject, String> {
|
|
let disk = source_files::load_project(entry, ProjectLimits::default())
|
|
.map_err(|error| format!("load source project: {error}"))?;
|
|
let errors: Vec<_> = disk
|
|
.diagnostics
|
|
.iter()
|
|
.filter(|issue| issue.diagnostic.severity == validation::Severity::Error)
|
|
.collect();
|
|
if !errors.is_empty() {
|
|
return Err(format!("source/discovery errors: {errors:?}"));
|
|
}
|
|
for issue in &disk.diagnostics {
|
|
eprintln!(
|
|
"Permitted non-error diagnostic in {}: {:?}",
|
|
entry.display(),
|
|
issue
|
|
);
|
|
}
|
|
let raw = disk.reachable_bundle();
|
|
if disk.bundle != raw {
|
|
return Err("detached/invalid discovered buffers are not admitted".into());
|
|
}
|
|
let compiled = compile_bundle(&raw, ProjectLimits::default())
|
|
.map_err(|issues| format!("source admission: {issues:?}"))?;
|
|
if compiled.bundle != raw {
|
|
return Err("source migration/normalization is not permitted".into());
|
|
}
|
|
if SCHEMA_VERSION != 7 || compiled.decoded.project.schema_version != 7 {
|
|
return Err("this oracle requires current schema 7 core and source".into());
|
|
}
|
|
validate_candidate(&compiled.decoded.project)
|
|
.map_err(|error| format!("model admission: {error}"))?;
|
|
Ok(compiled)
|
|
}
|
|
fn removed(base: &CompiledSourceProject, target: Id) -> CompiledSourceProject {
|
|
assert!(
|
|
base.decoded.project.contains(target),
|
|
"target absent from baseline"
|
|
);
|
|
let candidate = prepare_command(&base.decoded.project, &Command::Delete(target))
|
|
.expect("baseline Delete must be admissible");
|
|
assert!(
|
|
!candidate.project.contains(target),
|
|
"Delete must remove requested identity"
|
|
);
|
|
assert_ne!(
|
|
candidate.project, base.decoded.project,
|
|
"Delete must change model"
|
|
);
|
|
let result = update_bundle_with_identity_map(
|
|
base,
|
|
&candidate.project,
|
|
&candidate.identity_map,
|
|
ProjectLimits::default(),
|
|
)
|
|
.expect("ordinary source patch must be admissible");
|
|
assert_eq!(
|
|
result.decoded.project, candidate.project,
|
|
"source must exactly represent candidate"
|
|
);
|
|
assert_eq!(
|
|
result.bundle.files.keys().collect::<Vec<_>>(),
|
|
base.bundle.files.keys().collect::<Vec<_>>(),
|
|
"Delete must retain source files, including newly empty files"
|
|
);
|
|
result
|
|
}
|
|
|
|
// Independent model expectation: alter only the selected alias field. The public
|
|
// command must agree with it before the source updater receives the candidate.
|
|
fn assign_alias_only(project: &mut Project, target: Id, alias: &str) {
|
|
if project.id == target {
|
|
project.alias = alias.into();
|
|
} else if let Some(record) = project.elements.get_mut(&target) {
|
|
record.alias = alias.into();
|
|
} else if let Some(record) = project.relations.get_mut(&target) {
|
|
record.alias = alias.into();
|
|
} else if let Some(record) = project.diagrams.get_mut(&target) {
|
|
record.alias = alias.into();
|
|
} else if let Some(record) = project.artifact_links.get_mut(&target) {
|
|
record.alias = alias.into();
|
|
} else if let Some(record) = project.traces.get_mut(&target) {
|
|
record.alias = alias.into();
|
|
} else {
|
|
project
|
|
.diagrams
|
|
.values_mut()
|
|
.find_map(|view| view.nodes.get_mut(&target))
|
|
.expect("fixed alias target absent from baseline")
|
|
.alias = alias.into();
|
|
}
|
|
}
|
|
fn renamed(base: &CompiledSourceProject, target: Id, alias: &str) -> CompiledSourceProject {
|
|
assert_eq!(
|
|
SCHEMA_VERSION, 7,
|
|
"this oracle requires current schema 7 core"
|
|
);
|
|
assert_eq!(base.decoded.project.schema_version, 7);
|
|
let old_alias = base
|
|
.decoded
|
|
.project
|
|
.identities()
|
|
.into_iter()
|
|
.find_map(|(identity, old)| (identity == target).then(|| old.to_owned()))
|
|
.expect("fixed alias target absent from baseline");
|
|
assert_ne!(
|
|
old_alias, alias,
|
|
"rename mode requires an actual alias change"
|
|
);
|
|
let mut expected_project = base.decoded.project.clone();
|
|
assign_alias_only(&mut expected_project, target, alias);
|
|
let candidate = prepare_command(
|
|
&base.decoded.project,
|
|
&Command::SetAlias(target, alias.into()),
|
|
)
|
|
.expect("baseline alias rename must be admissible");
|
|
assert_eq!(
|
|
candidate.project, expected_project,
|
|
"only requested alias may change"
|
|
);
|
|
let identities: BTreeMap<_, _> = base
|
|
.decoded
|
|
.project
|
|
.identities()
|
|
.into_iter()
|
|
.map(|(identity, _)| (identity, identity))
|
|
.collect();
|
|
assert_eq!(
|
|
candidate.identity_map, identities,
|
|
"all UUIDs must remain unchanged"
|
|
);
|
|
let result = update_bundle_with_identity_map(
|
|
base,
|
|
&candidate.project,
|
|
&candidate.identity_map,
|
|
ProjectLimits::default(),
|
|
)
|
|
.expect("ordinary alias source patch must be admissible");
|
|
assert_eq!(
|
|
result.decoded.project, expected_project,
|
|
"complete alias-only expected model"
|
|
);
|
|
assert_eq!(
|
|
result.bundle.entry, base.bundle.entry,
|
|
"entry name must remain unchanged"
|
|
);
|
|
assert!(
|
|
result.bundle.files.keys().eq(base.bundle.files.keys()),
|
|
"file membership must remain unchanged"
|
|
);
|
|
assert_eq!(
|
|
result.index.lexical_owners, base.index.lexical_owners,
|
|
"lexical ownership must remain unchanged"
|
|
);
|
|
assert!(
|
|
base.index
|
|
.declarations
|
|
.iter()
|
|
.map(|(id, loc)| (id, &loc.path))
|
|
.eq(result
|
|
.index
|
|
.declarations
|
|
.iter()
|
|
.map(|(id, loc)| (id, &loc.path))),
|
|
"declaration file ownership must remain unchanged"
|
|
);
|
|
result
|
|
}
|
|
fn rename_fixture() -> CompiledSourceProject {
|
|
let raw = SourceBundle {
|
|
entry: "project.arcadia".into(),
|
|
files: BTreeMap::from([
|
|
(
|
|
"project.arcadia".into(),
|
|
format!(
|
|
"arcadia 7;\r\nproject rename_oracle \"Alias oracle\" id \"{}\" {{\r\n include \"target.arcadia\";\r\n include \"references.arcadia\";\r\n include \"notes.arcadia\";\r\n}}\r\n",
|
|
id(100)
|
|
),
|
|
),
|
|
(
|
|
"target.arcadia".into(),
|
|
format!(
|
|
"// Preserve rename_target comment 雨.\r\noa_activity rename_target \"Target engineering name\" id \"{}\" {{\r\n description \"Literal rename_target is not a reference.\";\r\n}}\r\noa_entity rename_team \"Team\" id \"{}\" {{}}\r\n",
|
|
id(101),
|
|
id(102)
|
|
),
|
|
),
|
|
(
|
|
"references.arcadia".into(),
|
|
format!(
|
|
"// Preserve @rename_target comment Ω.\r\nallocation rename_allocation \"Team performs target\" id \"{}\" {{\r\n source @rename_team; target @rename_target;\r\n}}\r\n",
|
|
id(103)
|
|
),
|
|
),
|
|
(
|
|
"notes.arcadia".into(),
|
|
"// Unrelated rename_target bytes 雨.\r\n\r\n".into(),
|
|
),
|
|
]),
|
|
};
|
|
compile_exact(&raw)
|
|
}
|
|
fn rename_self_check() {
|
|
let base = rename_fixture();
|
|
let expected = renamed(&base, id(101), "rename_target_union");
|
|
// This fixed fixture's independently authored source expectation does not
|
|
// call the command or updater and does not inspect any actual saved input.
|
|
let mut expected_text = base.bundle.clone();
|
|
for (path, before, after) in [
|
|
(
|
|
"target.arcadia",
|
|
"oa_activity rename_target ",
|
|
"oa_activity rename_target_union ",
|
|
),
|
|
(
|
|
"references.arcadia",
|
|
"target @rename_target;",
|
|
"target @rename_target_union;",
|
|
),
|
|
] {
|
|
let text = expected_text.files.get_mut(path).unwrap();
|
|
assert_eq!(text.matches(before).count(), 1, "exact fixed fixture token");
|
|
*text = text.replacen(before, after, 1);
|
|
}
|
|
assert_eq!(
|
|
expected.bundle, expected_text,
|
|
"only declaration/reference tokens change; comments, strings, CRLF and other files remain exact"
|
|
);
|
|
check(&expected, &compile_exact(&expected_text));
|
|
let mut independently_expected = base.decoded.project.clone();
|
|
independently_expected
|
|
.elements
|
|
.get_mut(&id(101))
|
|
.unwrap()
|
|
.alias = "rename_target_union".into();
|
|
assert_eq!(expected.decoded.project, independently_expected);
|
|
assert_eq!(
|
|
base.bundle.files["notes.arcadia"],
|
|
expected.bundle.files["notes.arcadia"]
|
|
);
|
|
assert_eq!(
|
|
base.bundle.files["project.arcadia"],
|
|
expected.bundle.files["project.arcadia"]
|
|
);
|
|
assert!(
|
|
!same(&expected, &base),
|
|
"rename must reject unchanged baseline"
|
|
);
|
|
let wrong = renamed(&base, id(101), "rename_target_wrong");
|
|
assert!(!same(&expected, &wrong), "rename must reject wrong alias");
|
|
let wrong_target = renamed(&base, id(102), "rename_team_union");
|
|
assert!(
|
|
!same(&expected, &wrong_target),
|
|
"rename must reject wrong target"
|
|
);
|
|
let mut partial = expected.bundle.clone();
|
|
let references = partial.files.get_mut("references.arcadia").unwrap();
|
|
*references = references.replacen("target @rename_target_union;", "target @rename_target;", 1);
|
|
assert!(
|
|
compile_bundle(&partial, ProjectLimits::default()).is_err(),
|
|
"a stale partial reference must fail source admission before equality"
|
|
);
|
|
let mut unrelated = expected.bundle.clone();
|
|
unrelated
|
|
.files
|
|
.get_mut("notes.arcadia")
|
|
.unwrap()
|
|
.push_str("// unrelated alteration\r\n");
|
|
let altered = compile_exact(&unrelated);
|
|
assert_eq!(altered.decoded.project, expected.decoded.project);
|
|
assert!(
|
|
!same(&expected, &altered),
|
|
"same model cannot hide altered unrelated file"
|
|
);
|
|
println!(
|
|
"PASS alias self-check: exact token expectation, full alias-only model/UUIDs, comments/strings/CRLF/other files, no-op/wrong alias/wrong target/partial reference/unrelated-file refusals"
|
|
);
|
|
}
|
|
|
|
fn same(expected: &CompiledSourceProject, actual: &CompiledSourceProject) -> bool {
|
|
expected.decoded.project == actual.decoded.project && expected.bundle == actual.bundle
|
|
}
|
|
fn check(expected: &CompiledSourceProject, actual: &CompiledSourceProject) {
|
|
assert_eq!(
|
|
actual.decoded.project, expected.decoded.project,
|
|
"complete Project differs"
|
|
);
|
|
assert_eq!(
|
|
actual.bundle, expected.bundle,
|
|
"complete SourceBundle differs (paths or exact UTF-8 bytes)"
|
|
);
|
|
}
|
|
fn id(n: u128) -> Id {
|
|
Id::from_u128(0xde1e7e00000040008000000000000000 + n)
|
|
}
|
|
fn fixture() -> CompiledSourceProject {
|
|
let mut p = Project::empty();
|
|
p.id = id(1);
|
|
p.alias = "delete_oracle".into();
|
|
p.name = "Delete oracle".into();
|
|
for (n, kind, name, parent) in [
|
|
(10, Kind::Activity, "Target work", None),
|
|
(11, Kind::Activity, "Untouched work", None),
|
|
(20, Kind::Scenario, "Reference scenario", None),
|
|
(21, Kind::Lifeline, "Target reference", Some(id(20))),
|
|
] {
|
|
let mut e = p.make_element(kind, Layer::Oa, name, parent).unwrap();
|
|
e.id = id(n);
|
|
e.alias = format!("delete_{n}");
|
|
p.elements.insert(e.id, e);
|
|
}
|
|
*p.elements.get_mut(&id(21)).unwrap().shared_mut().unwrap() = SharedBody::Lifeline {
|
|
represented: Some(id(10)),
|
|
};
|
|
for (view, placement) in [(30, 31), (40, 41)] {
|
|
let node = NodePlacement {
|
|
id: id(placement),
|
|
alias: format!("node_{placement}"),
|
|
element: id(10),
|
|
position: Position { x: 80., y: 80. },
|
|
size: Size {
|
|
width: 180.,
|
|
height: 90.,
|
|
},
|
|
collapsed: false,
|
|
port_anchors: vec![],
|
|
};
|
|
p.diagrams.insert(
|
|
id(view),
|
|
Diagram {
|
|
id: id(view),
|
|
alias: format!("view_{view}"),
|
|
name: format!("Work view {view}"),
|
|
description: String::new(),
|
|
layer: Layer::Oa,
|
|
projection: ProjectionKind::Allocation,
|
|
view_options: Default::default(),
|
|
nodes: BTreeMap::from([(node.id, node)]),
|
|
routes: vec![],
|
|
},
|
|
);
|
|
}
|
|
for (n, kind, layer, name, parent) in [
|
|
(12, Kind::Entity, Layer::Oa, "OA Dispatch team", None),
|
|
(
|
|
50,
|
|
Kind::Function,
|
|
Layer::Sa,
|
|
"SA Initialize controller",
|
|
None,
|
|
),
|
|
(
|
|
51,
|
|
Kind::StateMachine,
|
|
Layer::Sa,
|
|
"SA Controller modes",
|
|
None,
|
|
),
|
|
(52, Kind::Region, Layer::Sa, "SA Main region", Some(id(51))),
|
|
(53, Kind::State, Layer::Sa, "SA Ready", Some(id(52))),
|
|
] {
|
|
let mut e = p.make_element(kind, layer, name, parent).unwrap();
|
|
e.id = id(n);
|
|
e.alias = format!("delete_{n}");
|
|
p.elements.insert(e.id, e);
|
|
}
|
|
for (n, kind, name, source, target) in [
|
|
(
|
|
13,
|
|
RelationKind::Allocation,
|
|
"OA performs target work",
|
|
12,
|
|
10,
|
|
),
|
|
(54, RelationKind::StateAction, "SA Entry initialize", 53, 50),
|
|
] {
|
|
let generated = p.relate(kind, name, id(source), id(target));
|
|
let mut relation = p.relations.remove(&generated).unwrap();
|
|
relation.id = id(n);
|
|
relation.alias = format!("delete_{n}");
|
|
if let RelationBody::StateAction { phase, .. } = &mut relation.body {
|
|
*phase = StateActionKind::Entry;
|
|
}
|
|
p.relations.insert(relation.id, relation);
|
|
}
|
|
for (view, placement) in [(30, 32), (40, 42)] {
|
|
p.diagrams.get_mut(&id(view)).unwrap().nodes.insert(
|
|
id(placement),
|
|
NodePlacement {
|
|
id: id(placement),
|
|
alias: format!("node_{placement}"),
|
|
element: id(12),
|
|
position: Position { x: 40., y: 40. },
|
|
size: Size {
|
|
width: 380.,
|
|
height: 230.,
|
|
},
|
|
collapsed: false,
|
|
port_anchors: vec![],
|
|
},
|
|
);
|
|
}
|
|
let nodes = [
|
|
(61, 51, 40., 40., 620., 460.),
|
|
(62, 52, 60., 95., 580., 380.),
|
|
(63, 53, 100., 155., 320., 190.),
|
|
]
|
|
.into_iter()
|
|
.map(|(n, element, x, y, width, height)| {
|
|
(
|
|
id(n),
|
|
NodePlacement {
|
|
id: id(n),
|
|
alias: format!("node_{n}"),
|
|
element: id(element),
|
|
position: Position { x, y },
|
|
size: Size { width, height },
|
|
collapsed: false,
|
|
port_anchors: vec![],
|
|
},
|
|
)
|
|
})
|
|
.collect();
|
|
p.diagrams.insert(
|
|
id(60),
|
|
Diagram {
|
|
id: id(60),
|
|
alias: "sa_modes_view".into(),
|
|
name: "SA Controller modes view".into(),
|
|
description: String::new(),
|
|
layer: Layer::Sa,
|
|
projection: ProjectionKind::StateMachine { machine: id(51) },
|
|
view_options: Default::default(),
|
|
nodes,
|
|
routes: vec![],
|
|
},
|
|
);
|
|
validate_candidate(&p).unwrap();
|
|
let entry = language::format(&p).replacen("{\n", "{\n include \"notes.arcadia\";\n", 1);
|
|
let raw = SourceBundle {
|
|
entry: "project.arcadia".into(),
|
|
files: BTreeMap::from([
|
|
("project.arcadia".into(), entry),
|
|
(
|
|
"notes.arcadia".into(),
|
|
"// Preserve deletion evidence 雨.\r\n// Exact bytes matter.\r\n".into(),
|
|
),
|
|
]),
|
|
};
|
|
let compiled = compile_exact(&raw);
|
|
assert_eq!(compiled.decoded.project, p);
|
|
compiled
|
|
}
|
|
fn self_check() {
|
|
let base = fixture();
|
|
let expected = removed(&base, id(10));
|
|
check(&expected, &compile_exact(&expected.bundle));
|
|
assert!(!same(&expected, &base), "must reject no-op");
|
|
let wrong = removed(&base, id(11));
|
|
assert!(!same(&expected, &wrong), "must reject wrong deletion");
|
|
let placement = removed(&base, id(31));
|
|
assert!(placement.decoded.project.contains(id(10)));
|
|
assert!(placement.decoded.project.contains(id(41)));
|
|
assert!(
|
|
!same(&expected, &placement),
|
|
"placement removal is not semantic deletion"
|
|
);
|
|
assert!(!expected.decoded.project.contains(id(31)));
|
|
assert!(!expected.decoded.project.contains(id(41)));
|
|
assert!(
|
|
!expected.decoded.project.contains(id(13)),
|
|
"semantic deletion removes allocation"
|
|
);
|
|
assert!(
|
|
placement.decoded.project.contains(id(13)),
|
|
"placement removal retains allocation"
|
|
);
|
|
let entry_row = removed(&base, id(54));
|
|
assert!(
|
|
entry_row.decoded.project.contains(id(50)),
|
|
"Entry row removal retains function"
|
|
);
|
|
assert!(
|
|
entry_row.decoded.project.contains(id(53)),
|
|
"Entry row removal retains state"
|
|
);
|
|
assert!(
|
|
!same(&entry_row, &removed(&base, id(50))),
|
|
"row removal differs from function deletion"
|
|
);
|
|
assert!(
|
|
matches!(
|
|
expected.decoded.project.elements[&id(21)].shared(),
|
|
Some(SharedBody::Lifeline { represented: None })
|
|
),
|
|
"cross-reference must clear"
|
|
);
|
|
let extra = prepare_command(
|
|
&expected.decoded.project,
|
|
&Command::Rename(id(11), "Unrelated damage".into()),
|
|
)
|
|
.unwrap();
|
|
let unrelated = update_bundle(&expected, &extra.project, ProjectLimits::default()).unwrap();
|
|
assert!(
|
|
!same(&expected, &unrelated),
|
|
"must reject unrelated semantic change"
|
|
);
|
|
for text in [
|
|
"// Preserve deletion evidence 雨.\n// Exact bytes matter.\n",
|
|
"// Damaged\r\n",
|
|
] {
|
|
let mut raw = expected.bundle.clone();
|
|
raw.files.insert("notes.arcadia".into(), text.into());
|
|
let tampered = compile_exact(&raw);
|
|
assert_eq!(tampered.decoded.project, expected.decoded.project);
|
|
assert!(
|
|
!same(&expected, &tampered),
|
|
"must reject source-only changes"
|
|
);
|
|
}
|
|
let mut missing = expected.bundle.clone();
|
|
missing.files.remove("notes.arcadia");
|
|
assert!(compile_bundle(&missing, ProjectLimits::default()).is_err());
|
|
check(&base, &compile_exact(&base.bundle));
|
|
assert!(
|
|
!same(&base, &expected),
|
|
"restoration must reject deleted snapshot"
|
|
);
|
|
assert_eq!(
|
|
base.bundle.files["notes.arcadia"],
|
|
expected.bundle.files["notes.arcadia"]
|
|
);
|
|
disk_self_check(&base);
|
|
rename_self_check();
|
|
println!(
|
|
"PASS self-check: exact candidate/source, wrong target, no-op, unrelated changes, placement/model distinction, crossrefs, source-byte falsifiers, disk warning admission and missing-include/syntax rejection"
|
|
);
|
|
}
|
|
fn disk_self_check(base: &CompiledSourceProject) {
|
|
let nonce = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let dir = std::env::temp_dir().join(format!(
|
|
"arcadia-delete-oracle-{}-{nonce}",
|
|
std::process::id()
|
|
));
|
|
fs::create_dir(&dir).expect("create unique owned disk self-check directory");
|
|
for (path, text) in &base.bundle.files {
|
|
fs::write(dir.join(path), text).unwrap();
|
|
}
|
|
let entry = dir.join(&base.bundle.entry);
|
|
let disk = source_files::load_project(&entry, ProjectLimits::default()).unwrap();
|
|
assert!(
|
|
disk.diagnostics
|
|
.iter()
|
|
.any(|issue| issue.diagnostic.severity == validation::Severity::Warning),
|
|
"fixture must exercise legitimate warning admission"
|
|
);
|
|
check(
|
|
base,
|
|
&try_load(&entry).expect("valid fixture with completeness warnings must load"),
|
|
);
|
|
fs::remove_file(dir.join("notes.arcadia")).unwrap();
|
|
assert!(
|
|
try_load(&entry).is_err(),
|
|
"missing include must fail disk admission"
|
|
);
|
|
fs::write(
|
|
dir.join("notes.arcadia"),
|
|
&base.bundle.files["notes.arcadia"],
|
|
)
|
|
.unwrap();
|
|
fs::write(&entry, "this is not valid Arcadia source").unwrap();
|
|
assert!(
|
|
try_load(&entry).is_err(),
|
|
"malformed source must fail disk admission"
|
|
);
|
|
// Remove only exact files created above; no recursive deletion or borrowed fixture mutation.
|
|
for path in base.bundle.files.keys() {
|
|
fs::remove_file(dir.join(path)).unwrap();
|
|
}
|
|
fs::remove_dir(&dir).unwrap();
|
|
}
|
|
fn generate(dir: &Path) {
|
|
let baseline = fixture();
|
|
fs::create_dir(dir).expect("output directory must not already exist; parent must exist");
|
|
for (path, text) in &baseline.bundle.files {
|
|
fs::write(dir.join(path), text).expect("write new fixture");
|
|
}
|
|
check(&baseline, &load(&dir.join(&baseline.bundle.entry)));
|
|
println!("PASS generated exact fixture {}", dir.display());
|
|
for (n, label) in [
|
|
(10, "OA target activity"),
|
|
(12, "OA entity"),
|
|
(13, "OA allocation"),
|
|
(30, "first architecture view"),
|
|
(31, "first activity placement"),
|
|
(32, "first entity placement"),
|
|
(40, "second architecture view"),
|
|
(41, "second activity placement"),
|
|
(42, "second entity placement"),
|
|
(50, "SA Entry function"),
|
|
(51, "SA machine"),
|
|
(52, "SA region"),
|
|
(53, "SA Ready state"),
|
|
(54, "SA Entry row relation"),
|
|
(60, "SA state view"),
|
|
(63, "SA Ready placement"),
|
|
] {
|
|
println!("{label}: {}", id(n));
|
|
}
|
|
}
|
|
fn main() {
|
|
let args: Vec<_> = std::env::args().collect();
|
|
match args.get(1).map(String::as_str) {
|
|
Some("self-check") if args.len() == 2 => self_check(),
|
|
Some("generate") if args.len() == 3 => generate(Path::new(&args[2])),
|
|
Some("same") if args.len() == 4 => {
|
|
let base = load(Path::new(&args[2]));
|
|
let actual = load(Path::new(&args[3]));
|
|
check(&base, &actual);
|
|
println!("PASS same: complete Project and exact SourceBundle restored");
|
|
}
|
|
Some("renamed") if args.len() == 6 => {
|
|
let base = load(Path::new(&args[2]));
|
|
let target: Id = args[4].parse().expect("TARGET_UUID must be a UUID");
|
|
// Complete expected model and source are fixed before actual is loaded.
|
|
let expected = renamed(&base, target, &args[5]);
|
|
let actual = load(Path::new(&args[3]));
|
|
check(&expected, &actual);
|
|
println!(
|
|
"PASS renamed {target} to {}: complete baseline-derived alias-only Project and exact SourceBundle",
|
|
args[5]
|
|
);
|
|
}
|
|
Some("removed") if args.len() == 5 => {
|
|
let base = load(Path::new(&args[2]));
|
|
let target: Id = args[4].parse().expect("TARGET_UUID must be a UUID");
|
|
// Expected is fully fixed before actual is loaded.
|
|
let expected = removed(&base, target);
|
|
let actual = load(Path::new(&args[3]));
|
|
check(&expected, &actual);
|
|
println!(
|
|
"PASS removed {target}: exact baseline-derived Delete candidate and normal source patch"
|
|
);
|
|
}
|
|
_ => panic!(
|
|
"usage: renamed BASE_ENTRY ACTUAL_ENTRY TARGET_UUID NEW_ALIAS | removed BASE_ENTRY ACTUAL_ENTRY TARGET_UUID | same BASE_ENTRY ACTUAL_ENTRY | self-check | generate NEW_DIR"
|
|
),
|
|
}
|
|
}
|