use edgefirst_tensor::{
Colorimetry, CpuAccess, Error, PixelFormat, Tensor, TensorDyn, TensorMapTrait, TensorMemory,
TensorTrait,
};
#[cfg(target_os = "linux")]
#[path = "support/dma_require.rs"]
mod dma_require;
#[cfg(target_os = "linux")]
fn dma_or_skip(what: &str) -> bool {
dma_require::available_or_skip(edgefirst_tensor::is_dma_available(), what)
}
#[test]
fn host_roundtrip_sees_the_same_bytes() {
let t = Tensor::<u8>::image(
64,
32,
PixelFormat::Rgb,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.unwrap();
{
let mut m = t.map_write().unwrap();
m.as_mut_slice()[0] = 0xAB;
}
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t);
let desc = dyn_t.descriptor_pinned(Some(&pin));
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(imported.shape(), dyn_t.shape());
assert_eq!(imported.dtype(), dyn_t.dtype());
assert_eq!(imported.format(), dyn_t.format());
let m = imported.as_u8().unwrap().map_read().unwrap();
assert_eq!(m.as_slice()[0], 0xAB, "import must alias, not copy");
}
#[test]
fn planar_format_round_trips_through_import() {
let t = Tensor::<u8>::image(
640,
480,
PixelFormat::PlanarRgb,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.unwrap();
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t);
let desc = dyn_t.descriptor_pinned(Some(&pin));
assert_eq!(
desc.fourcc, 0,
"precondition: fourcc cannot express PlanarRgb"
);
assert_ne!(
desc.format, 0,
"precondition: format carries the real format"
);
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(
imported.format(),
Some(PixelFormat::PlanarRgb),
"must round-trip via format, not the fourcc==0 sentinel"
);
assert_eq!(imported.shape(), dyn_t.shape());
}
#[test]
fn rejects_unknown_version() {
let t = Tensor::<u8>::new(&[4], None, None).unwrap();
let mut desc = TensorDyn::from(t).descriptor();
desc.version = 9999;
let err = TensorDyn::import_descriptor(&desc).unwrap_err();
assert!(
matches!(err, Error::NotImplemented(_)),
"expected NotImplemented, got {err:?}"
);
}
#[test]
fn rejects_unknown_dtype_code() {
let t = Tensor::<u8>::new(&[4], None, None).unwrap();
let mut desc = TensorDyn::from(t).descriptor();
desc.dtype = 9999;
let err = TensorDyn::import_descriptor(&desc).unwrap_err();
assert!(
matches!(err, Error::NotImplemented(_)),
"expected NotImplemented, got {err:?}"
);
}
#[test]
fn colorimetry_round_trips_through_import() {
let t = Tensor::<u8>::image(
16,
16,
PixelFormat::Rgb,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.unwrap();
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t).with_colorimetry(Colorimetry::jfif());
let desc = dyn_t.descriptor_pinned(Some(&pin));
assert_ne!(desc.colorimetry, 0, "precondition: producer tagged jfif");
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(
imported.colorimetry(),
Some(Colorimetry::jfif()),
"import must unpack desc.colorimetry onto the reconstructed tensor"
);
}
#[test]
fn undefined_colorimetry_imports_as_none() {
let t = Tensor::<u8>::image(
16,
16,
PixelFormat::Rgb,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.unwrap();
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let desc = TensorDyn::from(t).descriptor_pinned(Some(&pin));
assert_eq!(desc.colorimetry, 0);
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(imported.colorimetry(), None);
}
#[test]
fn host_import_inherits_the_producers_capacity_headroom() {
let t = Tensor::<u8>::image(
295,
175,
PixelFormat::Nv12,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.unwrap();
let producer_capacity = t.capacity_bytes();
let producer_shape_bytes = t.size();
assert!(
producer_capacity > producer_shape_bytes,
"precondition: NV12 at an odd width must pad the allocation beyond \
the logical shape (capacity={producer_capacity}, shape_bytes={producer_shape_bytes})"
);
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t);
let desc = dyn_t.descriptor_pinned(Some(&pin));
assert_eq!(desc.capacity, producer_capacity as u64);
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(
imported.capacity_bytes(),
producer_capacity,
"the imported alias must inherit the producer's real headroom, not \
just the declared shape's byte size"
);
let mut imported = imported;
imported
.configure_image(295, 175, PixelFormat::Nv12)
.expect("configure_image must fit within the producer's real capacity");
}
#[test]
fn host_import_preserves_the_producers_row_stride_for_pool_reuse() {
let t = Tensor::<u8>::image(
1920,
1080,
PixelFormat::Nv12,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.unwrap();
assert_eq!(
t.row_stride(),
Some(1920),
"precondition: semi-planar always records it"
);
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t);
let desc = dyn_t.descriptor_pinned(Some(&pin));
let mut imported = TensorDyn::import_descriptor(&desc).unwrap();
imported
.configure_image(1280, 720, PixelFormat::Nv12)
.expect("fits within capacity");
assert_eq!(
imported.effective_row_stride(),
Some(1920),
"must preserve the producer's pool-sized pitch, not recompute a \
tighter one that only fits the smaller image"
);
}
#[test]
fn host_import_rejects_a_row_stride_that_would_overflow_capacity() {
let t = Tensor::<u8>::image(
16,
16,
PixelFormat::Rgb,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.unwrap();
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t);
let mut desc = dyn_t.descriptor_pinned(Some(&pin));
let malicious_stride = 1_000_000i64;
desc.strides[0] = malicious_stride;
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_ne!(
imported.row_stride(),
Some(malicious_stride as usize),
"an oversized stride must not be trusted just because set_row_stride's \
own validation is a minimum-only check"
);
}
#[test]
fn host_import_without_pin_fails() {
let t = Tensor::<u8>::new(&[4], Some(TensorMemory::Mem), None).unwrap();
let desc = TensorDyn::from(t).descriptor();
let err = TensorDyn::import_descriptor(&desc).unwrap_err();
assert!(
matches!(&err, Error::InvalidArgument(m) if m.contains("descriptive-only capsule")),
"expected the no-address refusal naming the producer-side fix, got {err:?}"
);
}
#[cfg(not(target_os = "linux"))]
#[test]
fn dmabuf_import_has_no_coverage_off_linux() {
use std::io::Write;
let _ = writeln!(
std::io::stderr(),
"SKIPPED: TensorDyn::import_descriptor's kind::DMABUF arm is untested on \
this platform (dma-buf is Linux-only). No aliasing coverage for the \
dma-buf import path outside a Linux run."
);
}
#[cfg(target_os = "linux")]
#[test]
fn dmabuf_roundtrip_sees_the_same_bytes() {
if !dma_or_skip("dmabuf_roundtrip_sees_the_same_bytes") {
return;
}
let t = Tensor::<u8>::image(
64,
32,
PixelFormat::Rgb,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.expect("a usable DMA heap allocates");
{
let mut m = t.map_write().unwrap();
m.as_mut_slice()[0] = 0xEF;
}
let dyn_t = TensorDyn::from(t);
let desc = dyn_t.descriptor(); assert_eq!(desc.kind, edgefirst_tensor::tensor_kind::DMABUF);
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(imported.shape(), dyn_t.shape());
let m = imported.as_u8().unwrap().map_read().unwrap();
assert_eq!(m.as_slice()[0], 0xEF, "dma-buf import must alias, not copy");
}
#[cfg(target_os = "linux")]
#[test]
fn imported_dmabuf_with_a_recorded_stride_is_still_cpu_mappable() {
if !dma_or_skip("imported_dmabuf_with_a_recorded_stride_is_still_cpu_mappable") {
return;
}
let owner = Tensor::<u8>::image(
64,
32,
PixelFormat::Rgb,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.expect("a usable DMA heap allocates");
let fd = TensorTrait::clone_fd(&owner).expect("clone dma-buf fd");
let mut imported = Tensor::<u8>::from_fd(fd, &[32, 64, 3], None).expect("import via from_fd");
imported.set_format(PixelFormat::Rgb).expect("set_format");
imported
.set_row_stride(64 * 3)
.expect("set_row_stride: a HAL-computed pitch for the import's own format/width");
{
let mut m = imported.map_write().expect(
"a HAL-computed, capacity-bounded stride must not be rejected on an imported \
DMA-BUF -- that is exactly the write path a cross-package decode takes",
);
m.as_mut_slice()[0] = 0xAB;
}
let m = owner
.map_read()
.expect("read back through the owning tensor");
assert_eq!(
m.as_slice()[0],
0xAB,
"write through the imported, now-strided alias must be visible to the owner"
);
}
#[cfg(target_os = "linux")]
#[test]
fn dmabuf_import_preserves_the_producers_row_stride_for_pool_reuse() {
if !dma_or_skip("dmabuf_import_preserves_the_producers_row_stride_for_pool_reuse") {
return;
}
let t = Tensor::<u8>::image(
1920,
1080,
PixelFormat::Nv12,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.expect("a usable DMA heap allocates");
assert_eq!(
t.row_stride(),
Some(1920),
"precondition: semi-planar always records it"
);
let dyn_t = TensorDyn::from(t);
let desc = dyn_t.descriptor();
let mut imported = TensorDyn::import_descriptor(&desc).unwrap();
imported
.configure_image(1280, 720, PixelFormat::Nv12)
.expect("fits within capacity");
assert_eq!(
imported.effective_row_stride(),
Some(1920),
"must preserve the producer's pool-sized pitch, not recompute a tighter one \
that only fits the smaller image"
);
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[test]
fn iosurface_roundtrip_sees_the_same_bytes() {
let t = Tensor::<u8>::image(
64,
32,
PixelFormat::Rgb,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
{
let mut m = t.map_write().unwrap();
m.as_mut_slice()[0] = 0xCD;
}
let dyn_t = TensorDyn::from(t);
let desc = dyn_t.descriptor(); assert_eq!(desc.kind, edgefirst_tensor::tensor_kind::IOSURFACE);
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(imported.shape(), dyn_t.shape());
assert_eq!(
imported.row_stride(),
Some(desc.strides()[0] as usize),
"an IOSurface import must carry the producer's pitch \
(restore_imported_row_stride)"
);
let m = imported.as_u8().unwrap().map_read().unwrap();
assert_eq!(
m.as_slice()[0],
0xCD,
"IOSurface import must alias, not copy"
);
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[test]
fn iosurface_import_rejects_dead_id() {
let t = Tensor::<u8>::image(
16,
16,
PixelFormat::Grey,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
let mut desc = TensorDyn::from(t).descriptor();
desc.handle = 0x7FFF_FFFF; let err = TensorDyn::import_descriptor(&desc).unwrap_err();
assert!(
matches!(err, Error::InvalidArgument(_)),
"expected InvalidArgument, got {err:?}"
);
}
#[test]
fn import_refuses_a_descriptor_advertising_an_unwaitable_fence() {
let t = Tensor::<u8>::image(
16,
8,
PixelFormat::Rgb,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.expect("alloc");
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t);
let mut desc = dyn_t.descriptor_pinned(Some(&pin));
TensorDyn::import_descriptor(&desc).expect("clean descriptor imports");
desc.flags |= edgefirst_tensor::protocol::flags::SYNC_PRESENT;
let err = TensorDyn::import_descriptor(&desc)
.expect_err("a descriptor advertising SYNC_PRESENT must be refused");
let msg = format!("{err}");
assert!(
msg.contains("SYNC_PRESENT"),
"the error must name what it refused, got: {msg}"
);
}
#[test]
fn import_refuses_a_d3d11_completion_with_no_fence_handle() {
let t = Tensor::<u8>::image(
16,
8,
PixelFormat::Rgb,
Some(TensorMemory::Mem),
CpuAccess::ReadWrite,
)
.expect("alloc");
let pin = t.pin_host(CpuAccess::ReadWrite).unwrap();
let dyn_t = TensorDyn::from(t);
let mut desc = dyn_t.descriptor_pinned(Some(&pin));
desc.kind = edgefirst_tensor::protocol::kind::D3D11_TEXTURE;
desc.flags |= edgefirst_tensor::protocol::flags::SYNC_PRESENT;
desc.sync = 7;
desc.ptr = edgefirst_tensor::protocol::SendPtr(std::ptr::null_mut());
let err = TensorDyn::import_descriptor(&desc)
.expect_err("a completion with no fence to read it on must be refused");
let msg = format!("{err}");
assert!(
matches!(err, Error::InvalidArgument(_)),
"expected InvalidArgument, got {err:?}"
);
assert!(
msg.contains("fence handle"),
"the error must name what it refused, got: {msg}"
);
}
struct MockPboOps {
storage: std::sync::Mutex<Vec<u8>>,
}
unsafe impl edgefirst_tensor::PboOps for MockPboOps {
fn map_buffer(
&self,
_buffer_id: u32,
size: usize,
) -> edgefirst_tensor::Result<edgefirst_tensor::PboMapping> {
let storage = self.storage.lock().expect("lock");
assert_eq!(storage.len(), size, "mock PBO size mismatch");
Ok(edgefirst_tensor::PboMapping {
ptr: storage.as_ptr() as *mut u8,
size,
})
}
fn unmap_buffer(&self, _buffer_id: u32) -> edgefirst_tensor::Result<()> {
Ok(())
}
fn delete_buffer(&self, _buffer_id: u32) {}
}
fn pbo_backed(buffer_id: u32, bytes: usize, shape: &[usize]) -> TensorDyn {
let ops = std::sync::Arc::new(MockPboOps {
storage: std::sync::Mutex::new(vec![0u8; bytes]),
}) as std::sync::Arc<dyn edgefirst_tensor::PboOps>;
let pbo = edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, bytes, shape, None, ops)
.expect("PboTensor::from_pbo");
Tensor::<u8>::from_pbo(pbo)
.expect("Tensor::from_pbo")
.into()
}
#[test]
fn a_pbo_descriptor_carries_the_vtable_pointer_not_a_host_address() {
let t = pbo_backed(77, 4 * 4, &[4, 4, 1]);
let vtable = t
.pbo_vtable_ptr()
.expect("a PBO-backed tensor has a client-state vtable");
assert!(!vtable.is_null(), "the vtable pointer must be real");
let d = t.descriptor();
assert_eq!(
d.kind,
edgefirst_tensor::tensor_kind::PBO,
"a PBO reports the PBO kind"
);
assert_eq!(
d.ptr.0 as *const std::ffi::c_void, vtable,
"`ptr` is the PBO vtable, which is what `import_descriptor`'s PBO arm \
calls through -- a host address here would be called as a vtable"
);
let d = t.descriptor_pinned(None);
assert_eq!(
d.ptr.0 as *const std::ffi::c_void, vtable,
"the pinned-descriptor entry point agrees with the plain one when \
there is no pin to prefer"
);
assert!(
matches!(
t.pin_host(CpuAccess::Read),
Err(edgefirst_tensor::Error::NotImplemented(_))
),
"a PBO must refuse pin_host, or `ptr` could carry a host address"
);
}
#[test]
fn pbo_import_preserves_a_views_parent_pitch() {
const W: usize = 64;
const H: usize = 64;
const SIDE: usize = 16;
const X0: usize = 8;
const Y0: usize = 8;
let mut parent = pbo_backed(88, W * H * 3, &[H, W, 3]);
parent
.set_format(PixelFormat::Rgb)
.expect("a packed RGB image");
let parent_pitch = parent
.effective_row_stride()
.expect("a formatted image reports a pitch");
assert_eq!(parent_pitch, W * 3, "precondition: the parent is tight");
let view = parent
.view(edgefirst_tensor::Region::new(X0, Y0, SIDE, SIDE))
.expect("view a narrow window");
assert_eq!(
view.row_stride(),
Some(parent_pitch),
"precondition: a multi-row view records the parent's pitch, not its own \
tight row"
);
let imported = TensorDyn::import_descriptor(&view.descriptor())
.expect("a PBO descriptor imports through its client-state vtable");
assert_eq!(
imported.pbo_id(),
Some(88),
"the import names the producer's GL buffer"
);
assert_eq!(
imported.effective_row_stride(),
Some(parent_pitch),
"the rebuilt view must step rows by the parent pitch ({parent_pitch}); \
{} is the window's own tight row, which shears every row after the \
first (issue #162)",
SIDE * 3
);
}
#[test]
fn pin_host_is_refused_by_a_pbo_on_both_backends() {
let host: TensorDyn = Tensor::<u8>::new(&[4, 4], Some(TensorMemory::Mem), None)
.expect("host allocation")
.into();
host.pin_host(CpuAccess::Read)
.expect("host memory has an address that outlives the call");
let pbo_t = pbo_backed(9, 16, &[4, 4, 1]);
match pbo_t.pin_host(CpuAccess::Read) {
Err(Error::NotImplemented(msg)) => assert!(
msg.contains("PBO"),
"the refusal must name the backing it applies to, got: {msg}"
),
other => panic!(
"a PBO has no host address outside its own map guard, so pin_host \
must refuse it on this backend exactly as it does on the other, got: {other:?}"
),
}
}
#[test]
fn a_pbo_descriptor_carries_any_buffer_id_and_refuses_a_missing_one() {
let t = pbo_backed(0, 16, &[4, 4, 1]);
let imported = TensorDyn::import_descriptor(&t.descriptor())
.expect("buffer id 0 is a descriptor value like any other");
assert_eq!(imported.pbo_id(), Some(0));
let mut desc = t.descriptor();
desc.handle = -1;
let err = TensorDyn::import_descriptor(&desc).unwrap_err();
assert!(
matches!(&err, Error::InvalidArgument(m) if m == "PBO descriptor carries no buffer id"),
"got {err:?}"
);
}
#[cfg(target_os = "linux")]
fn shm_relabelled_as_dmabuf(
first: u8,
) -> (
TensorDyn,
std::os::fd::OwnedFd,
edgefirst_tensor::TensorDesc,
) {
use std::os::fd::AsRawFd;
let t = Tensor::<u8>::new(&[16, 16], Some(TensorMemory::Shm), None).expect("shm allocation");
t.map_write().expect("map shm").as_mut_slice()[0] = first;
let t = TensorDyn::from(t);
let fd = t.clone_fd().expect("shm fd");
let mut desc = t.descriptor();
desc.kind = edgefirst_tensor::tensor_kind::DMABUF;
desc.handle = fd.as_raw_fd() as i64;
(t, fd, desc)
}
#[cfg(target_os = "linux")]
#[test]
fn a_dmabuf_descriptor_imports_a_dup_of_its_fd() {
let (_src, fd, desc) = shm_relabelled_as_dmabuf(0x5A);
let imported = TensorDyn::import_descriptor(&desc).expect("the dma-buf arm imports the fd");
drop(fd);
assert_eq!(
imported.memory(),
TensorMemory::Shm,
"from_fd classifies the tmpfs fd it was handed"
);
let m = imported.as_u8().unwrap().map_read().unwrap();
assert_eq!(m.as_slice()[0], 0x5A, "the import aliases the producer");
}
#[cfg(target_os = "linux")]
#[test]
fn a_dmabuf_descriptor_with_a_negative_handle_carries_no_fd() {
let (_src, _fd, mut desc) = shm_relabelled_as_dmabuf(0);
desc.handle = -1;
let err = TensorDyn::import_descriptor(&desc).unwrap_err();
assert!(
matches!(&err, Error::InvalidArgument(m) if m == "dma-buf descriptor carries no fd"),
"got {err:?}"
);
}
#[cfg(target_os = "linux")]
#[test]
fn a_dmabuf_descriptor_naming_fd_zero_is_not_refused_as_absent() {
let (_src, _fd, mut desc) = shm_relabelled_as_dmabuf(0);
desc.handle = 0;
if let Err(err) = TensorDyn::import_descriptor(&desc) {
assert!(
!err.to_string().contains("carries no fd"),
"fd 0 is a descriptor, got {err:?}"
);
}
}