#![allow(
clippy::expect_used,
clippy::cast_possible_truncation,
clippy::cast_precision_loss
)]
use crush_gpu::backend::{discover_gpu, GpuVendor};
#[test]
fn test_discover_backends_returns_result() {
let result = discover_gpu();
assert!(
result.is_ok(),
"discover_gpu should not error: {}",
result.err().map_or_else(String::new, |e| e.to_string())
);
if let Ok(Some(backend)) = &result {
let info = backend.gpu_info();
eprintln!(" GPU detected: {} ({})", info.name, info.vendor);
eprintln!(
" VRAM (max_buffer_size): {} bytes ({} GB)",
info.vram_bytes,
info.vram_bytes / (1024 * 1024 * 1024)
);
eprintln!(" API backend: {}", info.api_backend);
eprintln!(" Backend name: {}", backend.name());
} else {
eprintln!(" No GPU detected — CPU fallback will be used");
}
}
#[test]
fn test_gpu_detected_on_capable_machine() {
let result = discover_gpu().expect("discover_gpu should not error");
if let Some(backend) = &result {
let info = backend.gpu_info();
eprintln!(
" Found GPU: {} ({}) via {}",
info.name, info.vendor, info.api_backend
);
assert!(!info.name.is_empty(), "GPU name should not be empty");
assert!(
info.vram_bytes >= 2 * 1024 * 1024 * 1024,
"GPU should have >= 2GB VRAM"
);
}
}
#[test]
fn test_gpu_vendor_display() {
assert_eq!(GpuVendor::Nvidia.to_string(), "NVIDIA");
assert_eq!(GpuVendor::Amd.to_string(), "AMD");
assert_eq!(GpuVendor::Intel.to_string(), "Intel");
assert_eq!(GpuVendor::Apple.to_string(), "Apple");
assert_eq!(GpuVendor::Other.to_string(), "Other");
}
#[test]
fn test_gpu_decompression_roundtrip() {
use crush_gpu::backend::CompressedTile;
use crush_gpu::lz77;
use std::sync::atomic::AtomicBool;
let Some(backend) = discover_gpu().expect("discover_gpu should not error") else {
eprintln!(" No GPU — skipping GPU decompression test");
return;
};
let info = backend.gpu_info();
eprintln!(
" GPU decompression test on: {} ({})",
info.name, info.vendor
);
let cancel = AtomicBool::new(false);
let n: u8 = 32;
#[allow(clippy::cast_possible_truncation)]
let original = (0..256u16).map(|i| (i % 251) as u8).collect::<Vec<_>>();
let ns = usize::from(n);
let mut sub_streams: Vec<Vec<u8>> = vec![Vec::new(); ns];
for (i, &b) in original.iter().enumerate() {
sub_streams[i % ns].push(b);
}
let compressed_subs: Vec<Vec<u8>> = sub_streams
.iter()
.map(|ss| lz77::lz77_encode(ss, &lz77::STANDARD_CONFIG))
.collect();
let mut payload = Vec::new();
let mut running_offset: u32 = 0;
for cs in &compressed_subs {
payload.extend_from_slice(&running_offset.to_le_bytes());
#[allow(clippy::cast_possible_truncation)]
let len = cs.len() as u32;
running_offset += len;
}
for cs in &compressed_subs {
payload.extend_from_slice(cs);
}
let tile = CompressedTile {
data: payload,
#[allow(clippy::cast_possible_truncation)]
uncompressed_size: original.len() as u32,
sub_stream_count: n,
checksum: 0,
};
let results = backend
.decompress_tiles(&[tile], &cancel)
.expect("GPU decompress_tiles should succeed");
assert_eq!(results.len(), 1, "should have one tile result");
assert_eq!(
results[0].len(),
original.len(),
"decompressed size mismatch: got {} expected {}",
results[0].len(),
original.len()
);
assert_eq!(
results[0], original,
"GPU decompressed data should match original"
);
eprintln!(
" GPU decompression roundtrip PASSED ({} bytes)",
original.len()
);
}
#[test]
fn test_gdeflate_gpu_roundtrip() {
use crush_gpu::backend::CompressedTile;
use crush_gpu::gdeflate::gdeflate_compress_tile;
use std::sync::atomic::AtomicBool;
let Some(backend) = discover_gpu().expect("discover_gpu should not error") else {
eprintln!(" No GPU — skipping GDeflate GPU test");
return;
};
let info = backend.gpu_info();
eprintln!(" GDeflate GPU test on: {} ({})", info.name, info.vendor);
let cancel = AtomicBool::new(false);
for &size in &[1024u32, 32768, 65536] {
#[allow(clippy::cast_possible_truncation)]
let original: Vec<u8> = (0..size).map(|i| ((i * 7 + 13) % 256) as u8).collect();
let compressed = gdeflate_compress_tile(&original).expect("GDeflate compress");
let tile = CompressedTile {
data: compressed,
uncompressed_size: size,
sub_stream_count: 32,
checksum: 0,
};
let results = backend
.decompress_tiles_gdeflate(&[tile], &cancel)
.expect("GDeflate GPU decompress should succeed");
assert_eq!(results.len(), 1, "should have one tile result");
assert_eq!(
results[0].len(),
original.len(),
"GDeflate decompressed size mismatch for {size}B tile: got {} expected {}",
results[0].len(),
original.len()
);
assert_eq!(
results[0], original,
"GDeflate GPU data mismatch for {size}B tile"
);
eprintln!(" GDeflate GPU roundtrip PASSED ({size} bytes)");
}
}
#[test]
fn test_gdeflate_gpu_throughput() {
use crush_gpu::backend::CompressedTile;
use crush_gpu::gdeflate::gdeflate_compress_tile;
use std::sync::atomic::AtomicBool;
let Some(backend) = discover_gpu().expect("discover_gpu should not error") else {
eprintln!(" No GPU — skipping GDeflate throughput test");
return;
};
let cancel = AtomicBool::new(false);
let tile_size: usize = 65536;
let num_tiles: usize = 16;
let total_bytes = tile_size * num_tiles;
let mut tiles = Vec::with_capacity(num_tiles);
for t in 0..num_tiles {
#[allow(clippy::cast_possible_truncation)]
let data: Vec<u8> = (0..tile_size).map(|i| ((i + t * 31) % 256) as u8).collect();
let compressed = gdeflate_compress_tile(&data).expect("compress tile");
tiles.push(CompressedTile {
data: compressed,
uncompressed_size: tile_size as u32,
sub_stream_count: 32,
checksum: 0,
});
}
let start = std::time::Instant::now();
let results = backend
.decompress_tiles_gdeflate(&tiles, &cancel)
.expect("GDeflate GPU batch decompress");
let elapsed = start.elapsed();
assert_eq!(results.len(), num_tiles);
let throughput_mib = total_bytes as f64 / (1024.0 * 1024.0) / elapsed.as_secs_f64();
eprintln!(
" GDeflate GPU throughput: {throughput_mib:.1} MiB/s ({total_bytes} bytes in {elapsed:.2?})"
);
assert!(
throughput_mib > 3.0 || elapsed.as_millis() < 500,
"GDeflate GPU throughput {throughput_mib:.1} MiB/s too low"
);
}
#[test]
fn test_cpu_fallback_when_no_gpu() {
use crush_gpu::engine::{compress, decompress, EngineConfig};
use std::sync::atomic::AtomicBool;
let cancel = AtomicBool::new(false);
let config = EngineConfig {
force_cpu: true,
..EngineConfig::default()
};
let data = b"CPU fallback test data with repeated patterns! ".repeat(2000);
let compressed = compress(&data, &config, &cancel).expect("CPU compress should succeed");
let decompressed =
decompress(&compressed, &config, &cancel).expect("CPU decompress should succeed");
assert_eq!(data.as_slice(), decompressed.as_slice());
}