#![cfg(feature = "gpu")]
use blazehash::gpu::config::{GpuConfig, GpuConfigState};
use tempfile::TempDir;
fn temp_config_path() -> (TempDir, std::path::PathBuf) {
let dir = TempDir::new().unwrap();
let path = dir.path().join("config.toml");
(dir, path)
}
#[test]
fn test_config_no_file_returns_none() {
let (_dir, path) = temp_config_path();
let config = GpuConfig::load(&path);
assert!(config.is_none(), "no config file → no config");
}
#[test]
fn test_config_write_and_load_roundtrip() {
let (_dir, path) = temp_config_path();
let cfg = GpuConfig {
device: "NVIDIA RTX 3090".to_string(),
calibrated: "2026-04-09".to_string(),
threshold_single_mb: 48,
threshold_multi_mb: 3,
gpu_enabled: true,
};
cfg.save(&path).unwrap();
let loaded = GpuConfig::load(&path).unwrap();
assert_eq!(loaded.device, "NVIDIA RTX 3090");
assert_eq!(loaded.threshold_single_mb, 48);
assert!(loaded.gpu_enabled);
}
#[test]
fn test_config_corrupted_returns_none() {
let (_dir, path) = temp_config_path();
std::fs::write(&path, b"not valid toml {{{{").unwrap();
let config = GpuConfig::load(&path);
assert!(config.is_none(), "corrupted config → treat as missing");
}
#[test]
fn test_state_no_config_triggers_calibration() {
let (_dir, path) = temp_config_path();
let state = GpuConfigState::resolve(None, Some("NVIDIA RTX 3090"), &path);
assert_eq!(state, GpuConfigState::NeedsCalibration);
}
#[test]
fn test_state_same_device_enabled_returns_use_thresholds() {
let (_dir, path) = temp_config_path();
let cfg = GpuConfig {
device: "NVIDIA RTX 3090".to_string(),
calibrated: "2026-04-09".to_string(),
threshold_single_mb: 48,
threshold_multi_mb: 3,
gpu_enabled: true,
};
cfg.save(&path).unwrap();
let state = GpuConfigState::resolve(GpuConfig::load(&path), Some("NVIDIA RTX 3090"), &path);
assert_eq!(
state,
GpuConfigState::UseThresholds {
single_mb: 48,
multi_mb: 3
}
);
}
#[test]
fn test_state_same_device_disabled_returns_skip() {
let (_dir, path) = temp_config_path();
let cfg = GpuConfig {
device: "Intel UHD 630".to_string(),
calibrated: "2026-04-09".to_string(),
threshold_single_mb: 999,
threshold_multi_mb: 999,
gpu_enabled: false,
};
cfg.save(&path).unwrap();
let state = GpuConfigState::resolve(GpuConfig::load(&path), Some("Intel UHD 630"), &path);
assert_eq!(state, GpuConfigState::Skip);
}
#[test]
fn test_state_different_device_triggers_calibration() {
let (_dir, path) = temp_config_path();
let cfg = GpuConfig {
device: "Old GPU".to_string(),
calibrated: "2026-04-09".to_string(),
threshold_single_mb: 48,
threshold_multi_mb: 3,
gpu_enabled: true,
};
cfg.save(&path).unwrap();
let state = GpuConfigState::resolve(GpuConfig::load(&path), Some("New GPU"), &path);
assert_eq!(state, GpuConfigState::NeedsCalibration);
}
#[test]
fn test_state_no_gpu_adapter_returns_skip_leaves_config() {
let (_dir, path) = temp_config_path();
let cfg = GpuConfig {
device: "NVIDIA RTX 3090".to_string(),
calibrated: "2026-04-09".to_string(),
threshold_single_mb: 48,
threshold_multi_mb: 3,
gpu_enabled: true,
};
cfg.save(&path).unwrap();
let state = GpuConfigState::resolve(GpuConfig::load(&path), None, &path);
assert_eq!(state, GpuConfigState::Skip);
let reloaded = GpuConfig::load(&path).unwrap();
assert_eq!(reloaded.device, "NVIDIA RTX 3090");
}
#[test]
fn test_no_calibrate_flag_returns_conservative_defaults() {
let state = GpuConfigState::resolve_no_calibrate(Some("NVIDIA RTX 3090"));
assert_eq!(
state,
GpuConfigState::UseThresholds {
single_mb: blazehash::gpu::config::DEFAULT_THRESHOLD_SINGLE_MB,
multi_mb: blazehash::gpu::config::DEFAULT_THRESHOLD_MULTI_MB,
}
);
}
#[test]
fn test_backend_detect_returns_option() {
let backend = blazehash::gpu::backend::GpuBackend::detect();
if let Some(b) = backend {
assert!(!b.adapter_name().is_empty());
}
}
#[test]
fn test_backend_skips_software_renderers() {
let backend = blazehash::gpu::backend::GpuBackend::detect();
if let Some(b) = backend {
let name = b.adapter_name().to_lowercase();
assert!(!name.contains("warp"), "WARP is a software renderer");
assert!(
!name.contains("llvmpipe"),
"llvmpipe is a software renderer"
);
assert!(
!name.contains("software"),
"software renderer must be skipped"
);
}
}
#[test]
fn test_gpu_sha256_empty_input() {
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
eprintln!("No GPU — skipping GPU sha256 test");
return;
};
let hasher = blazehash::gpu::sha256::GpuSha256::new(&backend);
let result = hasher.hash(b"");
assert_eq!(
result,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
#[test]
fn test_gpu_sha256_abc() {
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
return;
};
let hasher = blazehash::gpu::sha256::GpuSha256::new(&backend);
let result = hasher.hash(b"abc");
assert_eq!(
result,
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn test_gpu_sha256_matches_cpu_for_various_sizes() {
use sha2::{Digest, Sha256};
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
return;
};
let hasher = blazehash::gpu::sha256::GpuSha256::new(&backend);
for size in [0usize, 1, 55, 56, 63, 64, 128, 1023, 4096] {
let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let gpu_result = hasher.hash(&data);
let cpu_result = hex::encode(Sha256::digest(&data));
assert_eq!(gpu_result, cpu_result, "mismatch at size={size}");
}
}
#[test]
fn test_gpu_sha256_large_file_matches_cpu() {
use sha2::{Digest, Sha256};
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
return;
};
let hasher = blazehash::gpu::sha256::GpuSha256::new(&backend);
let data = vec![0x42u8; 1024 * 1024]; let gpu_result = hasher.hash(&data);
let cpu_result = hex::encode(Sha256::digest(&data));
assert_eq!(gpu_result, cpu_result);
}
#[test]
fn test_gpu_md5_empty_input() {
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
eprintln!("No GPU — skipping GPU md5 test");
return;
};
let hasher = blazehash::gpu::md5::GpuMd5::new(&backend);
let result = hasher.hash(b"");
assert_eq!(result, "d41d8cd98f00b204e9800998ecf8427e");
}
#[test]
fn test_gpu_md5_abc() {
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
return;
};
let hasher = blazehash::gpu::md5::GpuMd5::new(&backend);
let result = hasher.hash(b"abc");
assert_eq!(result, "900150983cd24fb0d6963f7d28e17f72");
}
#[test]
fn test_gpu_md5_matches_cpu_for_various_sizes() {
use md5::{Digest, Md5};
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
return;
};
let hasher = blazehash::gpu::md5::GpuMd5::new(&backend);
for size in [0usize, 1, 55, 56, 63, 64, 128, 1023, 4096] {
let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let gpu_result = hasher.hash(&data);
let cpu_result = hex::encode(Md5::digest(&data));
assert_eq!(gpu_result, cpu_result, "mismatch at size={size}");
}
}
#[test]
fn test_gpu_md5_large_file_matches_cpu() {
use md5::{Digest, Md5};
let Some(backend) = blazehash::gpu::backend::GpuBackend::detect() else {
return;
};
let hasher = blazehash::gpu::md5::GpuMd5::new(&backend);
let data = vec![0x42u8; 1024 * 1024]; let gpu_result = hasher.hash(&data);
let cpu_result = hex::encode(Md5::digest(&data));
assert_eq!(gpu_result, cpu_result);
}
use blazehash::algorithm::Algorithm;
use blazehash::gpu::threshold::{should_use_gpu, GPU_ALGOS};
#[test]
fn test_gpu_algos_excludes_blake3() {
assert!(
!GPU_ALGOS.contains(&Algorithm::Blake3),
"BLAKE3 must not be in GPU_ALGOS"
);
assert!(
GPU_ALGOS.contains(&Algorithm::Sha256),
"SHA-256 must be in GPU_ALGOS"
);
assert!(
GPU_ALGOS.contains(&Algorithm::Md5),
"MD5 must be in GPU_ALGOS"
);
}
#[test]
fn test_should_use_gpu_skip_state_always_false() {
let state = GpuConfigState::Skip;
assert!(!should_use_gpu(100, &[Algorithm::Sha256], &state));
assert!(!should_use_gpu(100, &[Algorithm::Md5], &state));
}
#[test]
fn test_should_use_gpu_needs_calibration_always_false() {
let state = GpuConfigState::NeedsCalibration;
assert!(!should_use_gpu(100, &[Algorithm::Sha256], &state));
}
#[test]
fn test_should_use_gpu_blake3_only_always_false() {
let state = GpuConfigState::UseThresholds {
single_mb: 10,
multi_mb: 3,
};
assert!(!should_use_gpu(1000, &[Algorithm::Blake3], &state));
}
#[test]
fn test_should_use_gpu_single_algo_below_threshold() {
let state = GpuConfigState::UseThresholds {
single_mb: 48,
multi_mb: 3,
};
assert!(!should_use_gpu(10, &[Algorithm::Sha256], &state));
}
#[test]
fn test_should_use_gpu_single_algo_above_threshold() {
let state = GpuConfigState::UseThresholds {
single_mb: 48,
multi_mb: 3,
};
assert!(should_use_gpu(100, &[Algorithm::Sha256], &state));
}
#[test]
fn test_should_use_gpu_multi_algo_below_threshold() {
let state = GpuConfigState::UseThresholds {
single_mb: 48,
multi_mb: 3,
};
assert!(!should_use_gpu(
1,
&[Algorithm::Sha256, Algorithm::Md5],
&state
));
}
#[test]
fn test_should_use_gpu_multi_algo_above_threshold() {
let state = GpuConfigState::UseThresholds {
single_mb: 48,
multi_mb: 3,
};
assert!(should_use_gpu(
5,
&[Algorithm::Sha256, Algorithm::Md5],
&state
));
}
#[test]
fn test_should_use_gpu_mixed_algos_counts_only_gpu_eligible() {
let state = GpuConfigState::UseThresholds {
single_mb: 48,
multi_mb: 3,
};
assert!(should_use_gpu(
5,
&[Algorithm::Blake3, Algorithm::Sha256, Algorithm::Md5],
&state
));
}
#[test]
fn test_gpu_hash_file_sha256_matches_cpu() {
use blazehash::algorithm::Algorithm;
use blazehash::hash::hash_file;
use std::io::Write;
use tempfile::NamedTempFile;
if blazehash::gpu::backend::GpuBackend::detect().is_none() {
eprintln!("No GPU — skipping hash_file GPU integration test");
return;
}
let mut f = NamedTempFile::new().unwrap();
f.write_all(&vec![0x77u8; 1024 * 1024]).unwrap();
f.flush().unwrap();
let result1 = hash_file(f.path(), &[Algorithm::Sha256], false, false).unwrap();
let result2 = hash_file(f.path(), &[Algorithm::Sha256], false, false).unwrap();
assert_eq!(
result1.hashes[&Algorithm::Sha256],
result2.hashes[&Algorithm::Sha256],
"hash_file must be deterministic"
);
assert_eq!(result1.hashes[&Algorithm::Sha256].len(), 64);
}