use crate::{ChunkingError, Result, Chunk};
use std::fs::File;
use std::io::{Read, Write, BufReader, BufWriter};
use std::path::Path;
pub struct FileUtils;
impl FileUtils {
pub fn read_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
let mut file = File::open(path.as_ref())
.map_err(|e| ChunkingError::io_error(format!("Failed to open file: {}", e)))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.map_err(|e| ChunkingError::io_error(format!("Failed to read file: {}", e)))?;
Ok(buffer)
}
pub fn write_file<P: AsRef<Path>>(path: P, data: &[u8]) -> Result<()> {
let mut file = File::create(path.as_ref())
.map_err(|e| ChunkingError::io_error(format!("Failed to create file: {}", e)))?;
file.write_all(data)
.map_err(|e| ChunkingError::io_error(format!("Failed to write file: {}", e)))?;
file.flush()
.map_err(|e| ChunkingError::io_error(format!("Failed to flush file: {}", e)))?;
Ok(())
}
pub fn write_chunks_to_file<P: AsRef<Path>>(path: P, chunks: &[Chunk<'_>]) -> Result<()> {
let mut file = BufWriter::new(File::create(path.as_ref())
.map_err(|e| ChunkingError::io_error(format!("Failed to create file: {}", e)))?);
for chunk in chunks {
file.write_all(chunk.data)
.map_err(|e| ChunkingError::io_error(format!("Failed to write chunk: {}", e)))?;
}
file.flush()
.map_err(|e| ChunkingError::io_error(format!("Failed to flush file: {}", e)))?;
Ok(())
}
pub fn read_file_buffered<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
let file = File::open(path.as_ref())
.map_err(|e| ChunkingError::io_error(format!("Failed to open file: {}", e)))?;
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)
.map_err(|e| ChunkingError::io_error(format!("Failed to read file: {}", e)))?;
Ok(buffer)
}
}
pub struct ValidationUtils;
impl ValidationUtils {
pub fn verify_chunks(original: &[u8], chunks: &[Chunk<'_>]) -> Result<bool> {
let reconstructed: Vec<u8> = chunks
.iter()
.flat_map(|chunk| chunk.data.iter())
.copied()
.collect();
Ok(reconstructed == original)
}
pub fn validate_chunk_coverage(data_len: usize, chunks: &[Chunk<'_>]) -> Result<()> {
if chunks.is_empty() {
if data_len == 0 {
return Ok(());
} else {
return Err(ChunkingError::processing_error("No chunks found for non-empty data"));
}
}
let mut expected_start = 0;
for (i, chunk) in chunks.iter().enumerate() {
if chunk.start != expected_start {
return Err(ChunkingError::processing_error(
format!("Chunk {} starts at {} but expected {}", i, chunk.start, expected_start)
));
}
if chunk.is_empty() {
return Err(ChunkingError::processing_error(
format!("Chunk {} is empty", i)
));
}
expected_start = chunk.end();
}
if expected_start != data_len {
return Err(ChunkingError::processing_error(
format!("Chunks end at {} but data length is {}", expected_start, data_len)
));
}
Ok(())
}
}
pub struct TestDataGenerator;
impl TestDataGenerator {
pub fn generate_increasing_sequences(size: usize, seq_length: usize, seq_count: usize) -> Vec<u8> {
let mut data = vec![0u8; size];
let seq_spacing = size / seq_count.max(1);
for seq_idx in 0..seq_count {
let start_pos = seq_idx * seq_spacing;
let end_pos = (start_pos + seq_length).min(size);
for (i, pos) in (start_pos..end_pos).enumerate() {
if pos < data.len() {
data[pos] = (i % 256) as u8;
}
}
}
data
}
pub fn generate_decreasing_sequences(size: usize, seq_length: usize, seq_count: usize) -> Vec<u8> {
let mut data = vec![255u8; size];
let seq_spacing = size / seq_count.max(1);
for seq_idx in 0..seq_count {
let start_pos = seq_idx * seq_spacing;
let end_pos = (start_pos + seq_length).min(size);
for (i, pos) in (start_pos..end_pos).enumerate() {
if pos < data.len() {
data[pos] = (255 - (i % 256)) as u8;
}
}
}
data
}
pub fn generate_mixed_patterns(size: usize) -> Vec<u8> {
let mut data = Vec::with_capacity(size);
for i in 0..size {
let value = match i % 10 {
0..=4 => (i % 256) as u8, 5..=7 => (255 - (i % 256)) as u8, _ => ((i * 7) % 256) as u8, };
data.push(value);
}
data
}
pub fn generate_pseudo_random(size: usize, seed: u64) -> Vec<u8> {
let mut data = Vec::with_capacity(size);
let mut state = seed;
for _ in 0..size {
state = state.wrapping_mul(1103515245).wrapping_add(12345);
data.push((state >> 16) as u8);
}
data
}
}
pub struct PerfUtils;
impl PerfUtils {
pub fn measure_time<F, R>(f: F) -> (R, std::time::Duration)
where
F: FnOnce() -> R,
{
let start = std::time::Instant::now();
let result = f();
let duration = start.elapsed();
(result, duration)
}
pub fn calculate_throughput_mb_s(bytes: usize, duration: std::time::Duration) -> f64 {
if duration.as_secs_f64() == 0.0 {
return 0.0;
}
(bytes as f64) / (duration.as_secs_f64() * 1_000_000.0)
}
pub fn calculate_throughput_bytes_s(bytes: usize, duration: std::time::Duration) -> f64 {
if duration.as_secs_f64() == 0.0 {
return 0.0;
}
(bytes as f64) / duration.as_secs_f64()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{SeqChunking, ChunkingConfig};
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_file_read_write() {
let test_data = b"Hello, World! This is test data.";
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(test_data).unwrap();
temp_file.flush().unwrap();
let read_data = FileUtils::read_file(temp_file.path()).unwrap();
assert_eq!(read_data, test_data);
}
#[test]
fn test_chunk_verification() {
let chunker = SeqChunking::new();
let data = b"Test data for verification";
let chunks: Vec<_> = chunker.chunk_all(data).collect();
let is_valid = ValidationUtils::verify_chunks(data, &chunks).unwrap();
assert!(is_valid);
}
#[test]
fn test_chunk_coverage_validation() {
let chunker = SeqChunking::new();
let data = b"Test data for coverage validation";
let chunks: Vec<_> = chunker.chunk_all(data).collect();
ValidationUtils::validate_chunk_coverage(data.len(), &chunks).unwrap();
}
#[test]
fn test_test_data_generation() {
let data = TestDataGenerator::generate_increasing_sequences(1000, 10, 5);
assert_eq!(data.len(), 1000);
let data = TestDataGenerator::generate_decreasing_sequences(1000, 10, 5);
assert_eq!(data.len(), 1000);
let data = TestDataGenerator::generate_mixed_patterns(1000);
assert_eq!(data.len(), 1000);
let data = TestDataGenerator::generate_pseudo_random(1000, 42);
assert_eq!(data.len(), 1000);
}
#[test]
fn test_performance_utils() {
let (result, duration) = PerfUtils::measure_time(|| {
std::thread::sleep(std::time::Duration::from_millis(1));
42
});
assert_eq!(result, 42);
assert!(duration >= std::time::Duration::from_millis(1));
let throughput = PerfUtils::calculate_throughput_mb_s(1_000_000, std::time::Duration::from_secs(1));
assert_eq!(throughput, 1.0);
}
#[test]
fn test_write_chunks_to_file() {
let chunker = SeqChunking::new();
let original_data = b"Test data for chunk file writing";
let chunks: Vec<_> = chunker.chunk_all(original_data).collect();
let mut temp_file = NamedTempFile::new().unwrap();
FileUtils::write_chunks_to_file(temp_file.path(), &chunks).unwrap();
let reconstructed_data = FileUtils::read_file(temp_file.path()).unwrap();
assert_eq!(reconstructed_data, original_data);
}
}