use pictor_model::{model_merge::WeightTensor, Checkpoint, CheckpointError, CheckpointTensor};
#[test]
fn checkpoint_new_empty() {
let ck = Checkpoint::new();
assert_eq!(ck.tensors.len(), 0, "expected 0 tensors");
assert_eq!(ck.num_params(), 0, "expected 0 params");
assert_eq!(ck.metadata.len(), 0, "expected empty metadata");
}
#[test]
fn checkpoint_add_tensor() {
let mut ck = Checkpoint::new();
let t = CheckpointTensor::new("weight", vec![1.0, 2.0, 3.0, 4.0], vec![2, 2]);
ck.add_tensor(t);
assert_eq!(ck.tensors.len(), 1);
assert_eq!(ck.num_params(), 4);
}
#[test]
fn checkpoint_set_get_metadata() {
let mut ck = Checkpoint::new();
ck.set_metadata("step", "42");
ck.set_metadata("loss", "0.123");
assert_eq!(ck.get_metadata("step"), Some("42"));
assert_eq!(ck.get_metadata("loss"), Some("0.123"));
assert_eq!(ck.get_metadata("missing"), None);
}
#[test]
fn checkpoint_get_nonexistent_tensor() {
let ck = Checkpoint::new();
assert!(ck.get_tensor("no_such_tensor").is_none());
}
#[test]
fn checkpoint_total_bytes() {
let mut ck = Checkpoint::new();
ck.add_tensor(CheckpointTensor::new("a", vec![0.0f32; 10], vec![10]));
ck.add_tensor(CheckpointTensor::new("b", vec![0.0f32; 6], vec![2, 3]));
assert_eq!(ck.total_bytes(), (10 + 6) * 4);
}
#[test]
fn checkpoint_tensor_element_count() {
let t = CheckpointTensor::new("x", vec![0.0f32; 24], vec![2, 3, 4]);
assert_eq!(t.element_count(), 24);
}
#[test]
fn checkpoint_tensor_size_bytes() {
let t = CheckpointTensor::new("x", vec![0.0f32; 24], vec![2, 3, 4]);
assert_eq!(t.size_bytes(), 24 * 4);
}
#[test]
fn checkpoint_write_read_empty() {
let ck = Checkpoint::new();
let mut buf = Vec::<u8>::new();
ck.write_to(&mut buf).expect("write_to failed");
let loaded = Checkpoint::read_from(&mut buf.as_slice()).expect("read_from failed");
assert_eq!(loaded.version, 1);
assert_eq!(loaded.tensors.len(), 0);
assert_eq!(loaded.metadata.len(), 0);
}
#[test]
fn checkpoint_write_read_one_tensor() {
let mut ck = Checkpoint::new();
let original_data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
ck.add_tensor(CheckpointTensor::new(
"linear.weight",
original_data.clone(),
vec![2, 3],
));
let mut buf = Vec::<u8>::new();
ck.write_to(&mut buf).expect("write failed");
let loaded = Checkpoint::read_from(&mut buf.as_slice()).expect("read failed");
assert_eq!(loaded.tensors.len(), 1);
let t = &loaded.tensors[0];
assert_eq!(t.name, "linear.weight");
assert_eq!(t.shape, vec![2u64, 3]);
assert_eq!(t.data, original_data);
}
#[test]
fn checkpoint_write_read_metadata() {
let mut ck = Checkpoint::new();
ck.set_metadata("epoch", "5");
ck.set_metadata("lr", "0.0001");
ck.set_metadata("model", "qwen3-8b");
let mut buf = Vec::<u8>::new();
ck.write_to(&mut buf).expect("write failed");
let loaded = Checkpoint::read_from(&mut buf.as_slice()).expect("read failed");
assert_eq!(loaded.get_metadata("epoch"), Some("5"));
assert_eq!(loaded.get_metadata("lr"), Some("0.0001"));
assert_eq!(loaded.get_metadata("model"), Some("qwen3-8b"));
}
#[test]
fn checkpoint_write_read_multiple_tensors() {
let mut ck = Checkpoint::new();
ck.add_tensor(CheckpointTensor::new(
"embed.weight",
vec![0.1, 0.2],
vec![1, 2],
));
ck.add_tensor(CheckpointTensor::new(
"layer.0.attn.q_proj",
vec![1.0, -1.0, 0.5, -0.5, 2.0, -2.0],
vec![2, 3],
));
ck.add_tensor(CheckpointTensor::new(
"lm_head.bias",
vec![0.0, 0.01],
vec![2],
));
let mut buf = Vec::<u8>::new();
ck.write_to(&mut buf).expect("write failed");
let loaded = Checkpoint::read_from(&mut buf.as_slice()).expect("read failed");
assert_eq!(loaded.tensors.len(), 3);
assert_eq!(loaded.tensors[0].name, "embed.weight");
assert_eq!(loaded.tensors[1].name, "layer.0.attn.q_proj");
assert_eq!(loaded.tensors[2].name, "lm_head.bias");
assert_eq!(
loaded.tensors[1].data,
vec![1.0f32, -1.0, 0.5, -0.5, 2.0, -2.0]
);
}
#[test]
fn checkpoint_write_read_large_tensor() {
const N: usize = 10_000;
let data: Vec<f32> = (0..N).map(|i| i as f32 / N as f32).collect();
let mut ck = Checkpoint::new();
ck.add_tensor(CheckpointTensor::new("big", data.clone(), vec![N as u64]));
let mut buf = Vec::<u8>::new();
ck.write_to(&mut buf).expect("write failed");
let loaded = Checkpoint::read_from(&mut buf.as_slice()).expect("read failed");
assert_eq!(loaded.tensors[0].data.len(), N);
assert!(loaded.tensors[0].data == data, "large tensor data mismatch");
}
#[test]
fn checkpoint_save_load_tempfile() {
let mut ck = Checkpoint::new();
ck.set_metadata("test", "tempfile");
let pi = std::f32::consts::PI;
let e = std::f32::consts::E;
ck.add_tensor(CheckpointTensor::new("w", vec![pi, e], vec![2]));
let tmp_path = std::env::temp_dir().join("pictor_ck_test_save_load.bin");
ck.save(&tmp_path).expect("save failed");
let loaded = Checkpoint::load(&tmp_path).expect("load failed");
assert_eq!(loaded.get_metadata("test"), Some("tempfile"));
let t = loaded.get_tensor("w").expect("tensor 'w' not found");
assert_eq!(t.data, vec![std::f32::consts::PI, std::f32::consts::E]);
let _ = std::fs::remove_file(&tmp_path);
}
#[test]
fn checkpoint_invalid_magic() {
let bad_bytes = b"NOPE\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let result = Checkpoint::read_from(&mut bad_bytes.as_slice());
match result {
Err(CheckpointError::InvalidMagic(m)) => {
assert_eq!(&m, b"NOPE");
}
other => panic!("expected InvalidMagic, got {other:?}"),
}
}
#[test]
fn checkpoint_unsupported_version() {
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(b"OXCK");
bytes.extend_from_slice(&99u32.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes());
let result = Checkpoint::read_from(&mut bytes.as_slice());
match result {
Err(CheckpointError::UnsupportedVersion(99)) => {}
other => panic!("expected UnsupportedVersion(99), got {other:?}"),
}
}
#[test]
fn checkpoint_truncated_data() {
let mut ck = Checkpoint::new();
ck.add_tensor(CheckpointTensor::new("x", vec![1.0f32, 2.0, 3.0], vec![3]));
let mut buf = Vec::<u8>::new();
ck.write_to(&mut buf).expect("write failed");
let mut reader = &buf[..buf.len().saturating_sub(8)];
let result = Checkpoint::read_from(&mut reader);
assert!(result.is_err(), "expected error on truncated input");
}
#[test]
fn checkpoint_from_to_weight_tensor() {
let wt = WeightTensor::new("layer.weight", vec![0.5f32, -0.5, 1.0, -1.0], vec![2, 2]);
let ct = CheckpointTensor::from_weight_tensor(&wt);
assert_eq!(ct.name, "layer.weight");
assert_eq!(ct.shape, vec![2u64, 2]);
assert_eq!(ct.data, vec![0.5f32, -0.5, 1.0, -1.0]);
let wt2 = ct.to_weight_tensor();
assert_eq!(wt2.name, "layer.weight");
assert_eq!(wt2.shape, vec![2usize, 2]);
assert_eq!(wt2.data, wt.data);
}
#[test]
fn metadata_serialize_deserialize() {
let mut ck = Checkpoint::new();
ck.set_metadata("a", "hello");
ck.set_metadata("b", "world 42");
ck.set_metadata("c", "3.14");
let mut buf = Vec::<u8>::new();
ck.write_to(&mut buf).expect("write failed");
let loaded = Checkpoint::read_from(&mut buf.as_slice()).expect("read failed");
assert_eq!(loaded.get_metadata("a"), Some("hello"));
assert_eq!(loaded.get_metadata("b"), Some("world 42"));
assert_eq!(loaded.get_metadata("c"), Some("3.14"));
assert_eq!(loaded.metadata.len(), 3);
}