use std::path::{Path, PathBuf};
use ara_core::parse_sources;
fn fixtures() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
fn read(rel: &str) -> String {
std::fs::read_to_string(fixtures().join(rel)).unwrap_or_else(|e| panic!("read {rel}: {e}"))
}
#[test]
#[cfg(feature = "native")]
fn official_fixtures_are_clean() {
for dir in ["minimal-artifact", "resnet-ara-example"] {
let path = fixtures().join("official").join(dir);
let (manifest, report) =
ara_core::parse_dir(&path).unwrap_or_else(|r| panic!("{dir} failed: {r}"));
assert!(report.is_ok(), "{dir} has errors: {report}");
assert!(report.warnings().is_empty(), "{dir} has warnings: {report}");
assert!(!manifest.nodes.is_empty(), "{dir} produced no nodes");
}
}
#[test]
#[cfg(feature = "native")]
fn minimal_manifest_snapshot() {
let path = fixtures().join("official/minimal-artifact");
let (manifest, _) = ara_core::parse_dir(&path).expect("ok");
insta::assert_json_snapshot!("minimal_manifest", manifest);
}
#[test]
#[cfg(feature = "native")]
fn resnet_manifest_snapshot() {
let path = fixtures().join("official/resnet-ara-example");
let (manifest, _) = ara_core::parse_dir(&path).expect("ok");
insta::assert_json_snapshot!("resnet_manifest", manifest);
}
#[test]
#[cfg(feature = "native")]
fn parse_is_deterministic() {
let path = fixtures().join("official/resnet-ara-example");
let (a, _) = ara_core::parse_dir(&path).expect("ok");
let (b, _) = ara_core::parse_dir(&path).expect("ok");
let ja = serde_json::to_string_pretty(&a).unwrap();
let jb = serde_json::to_string_pretty(&b).unwrap();
assert_eq!(ja, jb);
}
#[test]
#[cfg(feature = "native")]
fn parse_dir_resolves_bindings() {
let path = fixtures().join("official/minimal-artifact");
let (manifest, report) = ara_core::parse_dir(&path).expect("ok");
assert!(!manifest.bindings.is_empty());
assert!(
!report
.warnings()
.iter()
.any(|w| w.message.contains("unresolved"))
);
}
#[test]
fn pivot_deadend_snapshot() {
let yaml = read("synthetic/pivot_deadend.yaml");
let (manifest, _) = parse_sources(&yaml, None).expect("ok");
insta::assert_json_snapshot!("pivot_deadend_manifest", manifest);
}
#[test]
fn pivot_deadend_has_no_field_warnings() {
let yaml = read("synthetic/pivot_deadend.yaml");
let (_manifest, report) = parse_sources(&yaml, None).expect("ok");
let widened = [
"hypothesis",
"failure_mode",
"lesson",
"from",
"to",
"trigger",
];
for w in report.warnings() {
for field in widened {
assert!(
!w.message.contains(field),
"unexpected warning mentioning `{field}`: {}",
w.message
);
}
}
}
#[test]
fn root_single_dialect_normalizes() {
let yaml = read("synthetic/root_single.yaml");
let (manifest, report) = parse_sources(&yaml, None).expect("ok");
assert!(report.is_ok());
assert_eq!(manifest.nodes.len(), 2);
assert_eq!(manifest.links.len(), 1); }
#[test]
fn broken_claim_ref_errors() {
let yaml = read("broken/broken_claim_ref.yaml");
let err = parse_sources(&yaml, Some("## C01: only claim\n")).unwrap_err();
assert!(
err.errors()
.iter()
.any(|d| d.message.contains("unknown claim")),
"expected broken-claim error, got: {err}"
);
}
#[test]
fn dup_id_errors() {
let yaml = read("broken/dup_id.yaml");
let err = parse_sources(&yaml, None).unwrap_err();
assert!(
err.errors()
.iter()
.any(|d| d.message.contains("duplicate node id"))
);
}
#[test]
fn cycle_errors() {
let yaml = read("broken/cycle.yaml");
let err = parse_sources(&yaml, None).unwrap_err();
assert!(err.errors().iter().any(|d| d.message.contains("cycle")));
}
#[test]
fn ambiguous_root_errors() {
let yaml = read("broken/ambiguous_root.yaml");
let err = parse_sources(&yaml, None).unwrap_err();
assert!(err.errors().iter().any(|d| d.message.contains("both")));
}
#[test]
#[cfg(feature = "native")]
fn missing_dir_is_clean_error() {
let err = ara_core::parse_dir(Path::new("/no/such/ara/dir")).unwrap_err();
assert!(!err.is_ok());
assert!(err.errors()[0].message.contains("cannot read"));
}
#[test]
#[cfg(feature = "native")]
fn self_composing_policies_snapshot() {
let path = fixtures().join("corpus/paperbench/self-composing-policies");
let (manifest, _) = ara_core::parse_dir(&path).expect("ok");
let paper = manifest.paper.as_ref().expect("paper present");
assert_eq!(
paper.title.as_deref(),
Some("Self-Composing Policies for Scalable Continual Reinforcement Learning")
);
assert_eq!(paper.year.as_deref(), Some("2024"));
assert_eq!(
manifest.concepts.first().map(|c| c.term.as_str()),
Some("CompoNet")
);
assert_eq!(manifest.related_work.len(), 9);
assert_eq!(manifest.related_work[0].id, "RW01");
assert_eq!(manifest.recipes.len(), 4); assert_eq!(manifest.exhibits.len(), 9);
assert!(!manifest.node_exhibits.is_empty());
assert!(!manifest.built_on.is_empty());
let n07_exhibits: Vec<&str> = manifest
.node_exhibits
.iter()
.filter(|ne| ne.node.as_str() == "N07")
.map(|ne| ne.exhibit.as_str())
.collect();
assert_eq!(
n07_exhibits,
vec!["fig3_scalability", "figb1_memory_growth"],
"N07 node_exhibits must be exactly the two scalability exhibits"
);
let n07_rw: Vec<&str> = manifest
.built_on
.iter()
.filter(|b| b.node.as_str() == "N07")
.map(|b| b.related_work.as_str())
.collect();
assert!(
n07_rw.contains(&"RW01") && n07_rw.contains(&"RW09"),
"N07 built_on must include RW01 and RW09, got: {n07_rw:?}"
);
insta::assert_json_snapshot!("self_composing_policies_manifest", manifest, {
".exhibits[].body" => "[exhibit body redacted]",
".recipes[].body" => "[recipe body redacted]",
});
}
#[test]
#[cfg(feature = "native")]
fn evidence_header_variants_resolve_end_to_end() {
let path = fixtures().join("evidence/e2e-variants");
let (manifest, report) = ara_core::parse_dir(&path).expect("ok");
assert!(report.is_ok(), "must not error: {report}");
let by_id = |id: &str| manifest.exhibits.iter().find(|e| e.id == id);
let backtick = by_id("t_backtick").expect("t_backtick exhibit");
assert_eq!(backtick.claims, vec![ara_core::ClaimId::new("C01")]);
assert_eq!(backtick.source.as_deref(), Some("Table 1")); let keyrefs = by_id("t_keyrefs").expect("t_keyrefs exhibit");
assert_eq!(keyrefs.claims, vec![ara_core::ClaimId::new("C05")]);
assert_eq!(
keyrefs.description.as_deref(),
Some("Key-refs carries claims")
);
let dualext = by_id("f_dualext").expect("f_dualext exhibit");
assert_eq!(dualext.claims, vec![ara_core::ClaimId::new("C01")]);
let node_ex = |node: &str| -> Vec<&str> {
manifest
.node_exhibits
.iter()
.filter(|ne| ne.node.as_str() == node)
.map(|ne| ne.exhibit.as_str())
.collect()
};
let n02 = node_ex("N02");
assert!(
n02.contains(&"t_backtick") && n02.contains(&"f_dualext"),
"got: {n02:?}"
);
assert_eq!(node_ex("N03"), vec!["t_keyrefs"]);
let built = |node: &str| -> Vec<&str> {
manifest
.built_on
.iter()
.filter(|b| b.node.as_str() == node)
.map(|b| b.related_work.as_str())
.collect()
};
assert_eq!(built("N02"), vec!["RW01"]);
assert_eq!(built("N03"), vec!["RW02"]);
}
#[test]
#[cfg(feature = "native")]
fn evidence_malformed_warns_not_fatal() {
let path = fixtures().join("evidence/malformed");
let (manifest, report) = ara_core::parse_dir(&path).expect("Ok despite malformed evidence");
assert!(
report.is_ok(),
"malformed evidence must not error: {report}"
);
assert!(manifest.exhibits.iter().any(|e| e.id == "present"));
assert!(manifest.exhibits.iter().any(|e| e.id == "orphan"));
assert!(!manifest.exhibits.iter().any(|e| e.id == "ghost"));
assert!(
report
.warnings()
.iter()
.any(|w| w.path.contains("ghost") && w.message.contains("no body")),
"expected missing-file warning, got: {report}"
);
assert!(
report
.warnings()
.iter()
.any(|w| w.path.contains("orphan") && w.message.contains("no index row")),
"expected orphan-body warning, got: {report}"
);
assert!(
manifest.node_exhibits.is_empty(),
"no exhibit carries C09 → node_exhibits must be empty, got: {:?}",
manifest.node_exhibits
);
}
#[test]
fn manifest_forward_and_round_trip_compat() {
use ara_core::Manifest;
let old = r#"{
"nodes": [],
"links": [],
"bindings": [],
"claims": []
}"#;
let m: Manifest = serde_json::from_str(old).expect("old manifest deserializes via defaults");
assert!(m.paper.is_none());
assert!(m.exhibits.is_empty());
assert!(m.built_on.is_empty());
assert!(m.node_exhibits.is_empty());
assert!(m.related_work.is_empty());
assert!(m.concepts.is_empty());
assert!(m.problem.is_none());
assert!(m.recipes.is_empty());
let path = fixtures().join("evidence/e2e-variants");
#[cfg(feature = "native")]
{
let (populated, _) = ara_core::parse_dir(&path).expect("ok");
assert!(!populated.exhibits.is_empty());
assert!(!populated.node_exhibits.is_empty());
let json = serde_json::to_string(&populated).expect("serialize");
let back: Manifest = serde_json::from_str(&json).expect("deserialize");
assert_eq!(populated, back, "populated manifest must round-trip equal");
}
let _ = path;
}
#[test]
#[cfg(feature = "native")]
fn malformed_logic_files_warn_not_fatal() {
let path = fixtures().join("sections/malformed");
let (manifest, report) = ara_core::parse_dir(&path).expect("Ok despite malformed logic files");
assert!(report.is_ok(), "malformed logic must not error: {report}");
assert!(manifest.paper.is_none());
assert!(
report
.warnings()
.iter()
.any(|w| w.path == "PAPER.md" && w.message.contains("malformed")),
"expected PAPER.md malformed warning, got: {report}"
);
assert_eq!(manifest.concepts.len(), 1);
assert!(manifest.concepts[0].definition.is_none());
assert!(manifest.concepts[0].notation.is_some()); assert!(
report
.warnings()
.iter()
.any(|w| w.path.starts_with("concepts[") && w.message.contains("no definition")),
"expected concepts warning, got: {report}"
);
assert_eq!(manifest.related_work.len(), 1);
assert!(manifest.related_work[0].doi.is_none());
assert!(manifest.related_work[0].claims_affected.is_empty());
assert_eq!(manifest.related_work[0].kind.as_deref(), Some("baseline"));
assert!(
report
.warnings()
.iter()
.any(|w| w.path.starts_with("related_work[") && w.message.contains("no DOI")),
"expected related_work warning, got: {report}"
);
}
#[test]
#[cfg(feature = "native")]
fn absent_logic_files_add_no_warnings() {
let path = fixtures().join("sections/absent");
let (manifest, report) = ara_core::parse_dir(&path).expect("ok");
assert!(report.is_ok());
assert!(
report.warnings().is_empty(),
"absent logic files must not warn: {report}"
);
assert!(manifest.paper.is_none());
assert!(manifest.problem.is_none());
assert!(manifest.concepts.is_empty());
assert!(manifest.related_work.is_empty());
assert!(manifest.recipes.is_empty());
}