66 lines
2.4 KiB
Rust
66 lines
2.4 KiB
Rust
|
|
//! Round-trip stability for the `state.ncl` renderer (O1.5 / D9).
|
||
|
|
//!
|
||
|
|
//! Strategy: load the real `.ontology/state.ncl`, render it, re-parse the
|
||
|
|
//! rendered bytes, render again. The second render MUST produce the same
|
||
|
|
//! bytes as the first — confirming `render(parse(render(x)))` is a fixed
|
||
|
|
//! point. This is strictly stronger than a structural-equivalence check
|
||
|
|
//! because it inspects every byte of the canonical form.
|
||
|
|
|
||
|
|
use std::path::{Path, PathBuf};
|
||
|
|
use std::process::Command;
|
||
|
|
|
||
|
|
use ontoref_ontology::render::render_state_ncl;
|
||
|
|
use ontoref_ontology::types::StateConfig;
|
||
|
|
|
||
|
|
/// Project root that holds the `.ontoref/` spine.
|
||
|
|
///
|
||
|
|
/// Walks up from this crate until it finds the directory containing `.ontoref/`.
|
||
|
|
/// Post-ADR-048 (constellation) the spine sits at the constellation parent,
|
||
|
|
/// outside `code/`; pre-ADR-048 it was inside the workspace. Searching ancestors
|
||
|
|
/// resolves it under either layout instead of hard-coding a hop count.
|
||
|
|
fn workspace_root() -> PathBuf {
|
||
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||
|
|
.ancestors()
|
||
|
|
.find(|dir| dir.join(".ontoref").is_dir())
|
||
|
|
.map(Path::to_path_buf)
|
||
|
|
.expect("a `.ontoref/` spine must exist in an ancestor of CARGO_MANIFEST_DIR")
|
||
|
|
}
|
||
|
|
|
||
|
|
fn nickel_export_state(path: &Path) -> StateConfig {
|
||
|
|
let output = Command::new("nickel")
|
||
|
|
.arg("export")
|
||
|
|
.arg("--format")
|
||
|
|
.arg("json")
|
||
|
|
.arg(path)
|
||
|
|
.output()
|
||
|
|
.expect("nickel binary must be on PATH for this test");
|
||
|
|
assert!(
|
||
|
|
output.status.success(),
|
||
|
|
"nickel export failed for {}: {}",
|
||
|
|
path.display(),
|
||
|
|
String::from_utf8_lossy(&output.stderr)
|
||
|
|
);
|
||
|
|
serde_json::from_slice(&output.stdout).expect("state.ncl JSON deserialises to StateConfig")
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn render_then_reparse_then_render_is_idempotent() {
|
||
|
|
let root = workspace_root();
|
||
|
|
let source = ontoref_ontology::layout::resolve_section(&root, "ontology/state.ncl");
|
||
|
|
let initial = nickel_export_state(&source);
|
||
|
|
|
||
|
|
let bytes_a = render_state_ncl(&initial);
|
||
|
|
|
||
|
|
let tmp = tempfile::tempdir().expect("tempdir");
|
||
|
|
let staged = tmp.path().join("state.ncl");
|
||
|
|
std::fs::write(&staged, &bytes_a).expect("write staged");
|
||
|
|
|
||
|
|
let reparsed = nickel_export_state(&staged);
|
||
|
|
let bytes_b = render_state_ncl(&reparsed);
|
||
|
|
|
||
|
|
assert_eq!(
|
||
|
|
bytes_a, bytes_b,
|
||
|
|
"render(parse(render(state))) must produce identical bytes"
|
||
|
|
);
|
||
|
|
}
|