use std::rc::Rc;
use std::sync::Arc;
use noyalib::{ArcAnchor, RcAnchor, to_string, to_string_tracking_shared};
use serde::{Deserialize, Serialize};
#[test]
fn single_ref_emits_anchor_but_no_alias() {
let a: RcAnchor<String> = RcAnchor::from("hello".to_string());
let yaml = to_string_tracking_shared(&a).unwrap();
assert!(yaml.contains("&id001"));
assert!(!yaml.contains("*id001"));
}
#[test]
fn two_clones_emit_one_anchor_one_alias() {
let a: RcAnchor<String> = RcAnchor::from("shared".to_string());
let doc = vec![a.clone(), a];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert_eq!(yaml.matches("&id001").count(), 1);
assert_eq!(yaml.matches("*id001").count(), 1);
assert!(yaml.contains("shared"));
}
#[test]
fn three_clones_one_anchor_two_aliases() {
let a: RcAnchor<String> = RcAnchor::from("x".to_string());
let doc = vec![a.clone(), a.clone(), a];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert_eq!(yaml.matches("&id001").count(), 1);
assert_eq!(yaml.matches("*id001").count(), 2);
}
#[test]
fn distinct_allocations_with_equal_value_emit_two_anchors() {
let a: RcAnchor<String> = RcAnchor::from("same".to_string());
let b: RcAnchor<String> = RcAnchor::from("same".to_string());
let doc = vec![a, b];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert!(yaml.contains("&id001"));
assert!(yaml.contains("&id002"));
assert!(!yaml.contains("*id"));
}
#[test]
fn disjoint_does_not_leak_state_between_calls() {
let a: RcAnchor<String> = RcAnchor::from("one".to_string());
let y1 = to_string_tracking_shared(&vec![a.clone(), a]).unwrap();
assert!(y1.contains("&id001"));
let b: RcAnchor<String> = RcAnchor::from("two".to_string());
let y2 = to_string_tracking_shared(&vec![b.clone(), b]).unwrap();
assert!(y2.contains("&id001"));
assert!(!y2.contains("&id002"));
}
#[test]
fn tracking_opt_in_default_unchanged() {
let a: RcAnchor<String> = RcAnchor::from("hi".to_string());
let doc = vec![a.clone(), a];
let yaml = to_string(&doc).unwrap();
assert!(!yaml.contains("&id"));
assert!(!yaml.contains("*id"));
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Endpoint {
host: String,
port: u16,
}
#[test]
fn mapping_inner_anchor_is_valid_yaml() {
let cfg: RcAnchor<Endpoint> = RcAnchor::from(Endpoint {
host: "db.local".to_string(),
port: 5432,
});
let doc = vec![cfg.clone(), cfg];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert!(yaml.contains("&id001"));
assert!(yaml.contains("*id001"));
let parsed: Vec<Endpoint> = noyalib::from_str(&yaml).unwrap();
assert_eq!(parsed.len(), 2);
assert_eq!(parsed[0].host, "db.local");
assert_eq!(parsed[0].port, 5432);
assert_eq!(parsed[0], parsed[1]);
}
#[test]
fn nested_struct_with_shared_leaf() {
#[derive(Serialize)]
struct Root {
primary: RcAnchor<Endpoint>,
replica: RcAnchor<Endpoint>,
}
let shared: RcAnchor<Endpoint> = RcAnchor::from(Endpoint {
host: "db.local".to_string(),
port: 5432,
});
let root = Root {
primary: shared.clone(),
replica: shared,
};
let yaml = to_string_tracking_shared(&root).unwrap();
assert_eq!(yaml.matches("&id001").count(), 1);
assert_eq!(yaml.matches("*id001").count(), 1);
#[derive(Deserialize)]
struct Parsed {
primary: Endpoint,
replica: Endpoint,
}
let p: Parsed = noyalib::from_str(&yaml).unwrap();
assert_eq!(p.primary, p.replica);
}
#[test]
fn sequence_inner_anchor() {
let list: RcAnchor<Vec<i32>> = RcAnchor::from(vec![1, 2, 3]);
let doc = vec![list.clone(), list];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert!(yaml.contains("&id001"));
assert!(yaml.contains("*id001"));
let parsed: Vec<Vec<i32>> = noyalib::from_str(&yaml).unwrap();
assert_eq!(parsed, vec![vec![1, 2, 3], vec![1, 2, 3]]);
}
#[test]
fn arc_two_clones_emit_anchor_and_alias() {
let a: ArcAnchor<String> = ArcAnchor::from("arc".to_string());
let doc = vec![a.clone(), a];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert_eq!(yaml.matches("&id001").count(), 1);
assert_eq!(yaml.matches("*id001").count(), 1);
}
#[test]
fn arc_struct_shared() {
#[derive(Serialize)]
struct Root {
a: ArcAnchor<Endpoint>,
b: ArcAnchor<Endpoint>,
c: ArcAnchor<Endpoint>,
}
let shared: ArcAnchor<Endpoint> = ArcAnchor::from(Endpoint {
host: "h".to_string(),
port: 1,
});
let root = Root {
a: shared.clone(),
b: shared.clone(),
c: shared,
};
let yaml = to_string_tracking_shared(&root).unwrap();
assert_eq!(yaml.matches("&id001").count(), 1);
assert_eq!(yaml.matches("*id001").count(), 2);
}
#[test]
fn multiple_distinct_shared_pointers() {
let a: RcAnchor<String> = RcAnchor::from("A".to_string());
let b: RcAnchor<String> = RcAnchor::from("B".to_string());
let doc = vec![a.clone(), b.clone(), a, b];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert!(yaml.contains("&id001"));
assert!(yaml.contains("&id002"));
assert_eq!(yaml.matches("*id001").count(), 1);
assert_eq!(yaml.matches("*id002").count(), 1);
}
#[test]
fn round_trip_value_equivalence() {
let a: RcAnchor<String> = RcAnchor::from("payload".to_string());
let doc = vec![a.clone(), a.clone(), a];
let yaml = to_string_tracking_shared(&doc).unwrap();
let parsed: Vec<String> = noyalib::from_str(&yaml).unwrap();
assert_eq!(parsed, vec!["payload", "payload", "payload"]);
}
#[test]
fn identity_driven_not_value_driven() {
let a: RcAnchor<String> = RcAnchor::from("x".to_string());
let b: RcAnchor<String> = RcAnchor::from("x".to_string());
let c = a.clone();
let doc = vec![a, b, c];
let yaml = to_string_tracking_shared(&doc).unwrap();
assert!(yaml.contains("&id001"));
assert!(yaml.contains("&id002"));
assert_eq!(yaml.matches("*id001").count(), 1);
assert!(!yaml.contains("*id002"));
}
#[test]
fn state_cleared_between_calls_even_on_error_path() {
let a: RcAnchor<String> = RcAnchor::from("a".to_string());
let _ = to_string_tracking_shared(&vec![a.clone(), a]);
let b: RcAnchor<String> = RcAnchor::from("b".to_string());
let y = to_string_tracking_shared(&vec![b.clone(), b]).unwrap();
assert!(y.contains("&id001"));
assert!(y.contains("*id001"));
assert!(!y.contains("&id003"));
}
#[test]
fn plain_to_string_with_rcanchor_delegates_to_inner() {
let a: RcAnchor<i32> = RcAnchor::from(42);
let yaml = to_string(&a).unwrap();
assert_eq!(yaml.trim(), "42");
}
#[test]
fn arc_worker_thread_serializes_independently() {
let a: ArcAnchor<String> = ArcAnchor::from("worker".to_string());
let clone = a.clone();
let handle = std::thread::spawn(move || {
let doc = vec![clone.clone(), clone];
to_string_tracking_shared(&doc).unwrap()
});
let y = handle.join().unwrap();
assert!(y.contains("&id001"));
assert!(y.contains("*id001"));
drop(a);
}
#[test]
fn rc_usable_after_tracking_serialization() {
let a: RcAnchor<String> = RcAnchor::from("live".to_string());
let clone = a.clone();
let _ = to_string_tracking_shared(&vec![a.clone(), a]).unwrap();
assert_eq!(&**clone, "live");
assert!(Rc::strong_count(&clone.0) >= 1);
}
#[test]
fn arc_usable_after_tracking_serialization() {
let a: ArcAnchor<String> = ArcAnchor::from("live".to_string());
let clone = a.clone();
let _ = to_string_tracking_shared(&vec![a.clone(), a]).unwrap();
assert_eq!(&**clone, "live");
assert!(Arc::strong_count(&clone.0) >= 1);
}