use cgpu::*;
#[derive(Clone)]
struct Item {
input: usize,
output: usize,
}
impl WorkItem for Item {
fn bytes_in(&self) -> usize {
self.input
}
fn bytes_out(&self) -> usize {
self.output
}
}
#[test]
fn packer_batches_by_bytes() {
let items = vec![
Item {
input: 30,
output: 5,
},
Item {
input: 30,
output: 5,
},
Item {
input: 30,
output: 5,
},
];
let budget = GpuMemoryBudget::new(100, 0.90).unwrap();
let mut telemetry = GpuTelemetry::default();
let plan = pack_by_bytes(&items, budget, &mut telemetry).unwrap();
assert_eq!(plan.spans().len(), 2);
assert_eq!(plan.spans()[0].range(), 0..2);
assert_eq!(plan.spans()[1].range(), 2..3);
assert_eq!(telemetry.bytes_in, 90);
assert_eq!(telemetry.bytes_out, 15);
assert_eq!(telemetry.batch_count, 2);
}
#[test]
fn packer_rejects_items_above_safe_budget() {
let items = vec![Item {
input: 100,
output: 1,
}];
let budget = GpuMemoryBudget::new(100, 0.90).unwrap();
let mut telemetry = GpuTelemetry::default();
let error = pack_by_bytes(&items, budget, &mut telemetry).unwrap_err();
assert_eq!(
error,
GpuError::ItemExceedsSafeBudget {
item_index: 0,
item_bytes: 101,
safe_vram_bytes: 100,
}
);
}
#[test]
fn cpu_fallback_uses_existing_batch_contract() {
let items = vec![
Item {
input: 30,
output: 1,
},
Item {
input: 30,
output: 1,
},
Item {
input: 30,
output: 1,
},
];
let budget = GpuMemoryBudget::new(100, 0.90).unwrap();
let mut telemetry = GpuTelemetry::default();
let plan = pack_by_bytes(&items, budget, &mut telemetry).unwrap();
let output = run_cpu_fallback(
&GpuError::GpuFailure(GpuFailure::DeviceLost),
&items,
&plan,
&mut telemetry,
|batch| {
Ok(batch
.iter()
.map(|item| item.input.saturating_mul(2))
.collect())
},
)
.unwrap();
assert_eq!(output, vec![60, 60, 60]);
}
#[test]
fn buffer_pool_reuses_compatible_slots() {
let mut pool = BufferPool::default();
let first = pool.acquire(64, BufferUse::Input);
assert!(pool.release(first));
let second = pool.acquire(32, BufferUse::Input);
assert_eq!(first.id, second.id);
assert_eq!(pool.slot_count(), 1);
}
#[test]
fn resource_cache_keeps_long_lived_objects() {
let mut cache = ResourceCache::default();
assert_eq!(*cache.get_or_insert_with("pipeline:a", || 10), 10);
assert_eq!(*cache.get_or_insert_with("pipeline:a", || 20), 10);
assert_eq!(cache.len(), 1);
}
#[test]
fn telemetry_log_line_includes_required_buckets() {
let telemetry = GpuTelemetry {
pack_ms: 1.0,
gpu_ms: 2.0,
readback_ms: 3.0,
post_ms: 4.0,
bytes_in: 5,
bytes_out: 6,
batch_count: 7,
};
let line = telemetry.log_line("layer0");
assert!(line.contains("pack_ms=1.000"));
assert!(line.contains("gpu_ms=2.000"));
assert!(line.contains("readback_ms=3.000"));
assert!(line.contains("post_ms=4.000"));
assert!(line.contains("bytes_in=5"));
assert!(line.contains("bytes_out=6"));
assert!(line.contains("batch_count=7"));
}
#[test]
fn u32_helpers_round_trip_bytes() {
let bytes = u32_slice_bytes(&[7, 42]);
assert_eq!(read_u32(&bytes, 0).unwrap(), 7);
assert_eq!(read_u32(&bytes, 4).unwrap(), 42);
assert!(read_u32(&bytes, 8).is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn memory_estimates_from_wgpu_limits_without_static_ceiling() {
let limits = wgpu::Limits {
max_storage_buffers_per_shader_stage: 8,
max_storage_buffer_binding_size: 256 * 1024 * 1024,
max_buffer_size: 512 * 1024 * 1024,
..wgpu::Limits::downlevel_defaults()
};
let availability = estimate_gpu_memory_availability_from_limits(&limits, "no platform probe");
assert_eq!(availability.available_bytes, 1_879_048_192);
assert!(availability.is_estimate);
assert_eq!(availability.source, "wgpu.limits.estimate");
}