#[cfg(test)]
mod property_tests {
use crate::*;
mod tile_coord_properties {
use super::*;
#[test]
fn property_parent_child_relationship() {
for level in 0..10 {
for x in 0..10 {
for y in 0..10 {
let coord = TileCoord::new(level, x, y);
let children = coord.children();
for child in children {
if let Some(parent) = child.parent() {
assert_eq!(parent, coord);
}
}
}
}
}
}
#[test]
fn property_key_parsing_bijection() {
for level in 0..5 {
for x in 0..20 {
for y in 0..20 {
let coord = TileCoord::new(level, x, y);
let key = coord.key();
let parsed = TileCoord::from_key(&key).expect("Should parse");
assert_eq!(parsed, coord);
assert_eq!(parsed.key(), key);
}
}
}
}
#[test]
fn property_neighbor_count() {
for level in 1..5 {
let coord = TileCoord::new(level, 10, 10);
let neighbors = coord.neighbors();
let valid_count = neighbors.iter().filter_map(|&n| n).count();
assert_eq!(valid_count, 8);
}
}
#[test]
fn property_children_have_higher_level() {
for level in 0..15 {
let coord = TileCoord::new(level, 5, 5);
let children = coord.children();
for child in children {
assert_eq!(child.level, level + 1);
}
}
}
#[test]
fn property_parent_has_lower_level() {
for level in 1..15 {
let coord = TileCoord::new(level, 10, 10);
if let Some(parent) = coord.parent() {
assert_eq!(parent.level, level - 1);
}
}
}
}
mod cache_properties {
use super::*;
#[test]
fn property_cache_hit_after_put() {
let mut cache = TileCache::new(10000);
for i in 0..100 {
let coord = TileCoord::new(0, i, 0);
let data = vec![i as u8; 100];
cache.put(coord, data.clone(), i as f64).expect("Put");
let retrieved = cache.get(&coord, (i + 1) as f64);
assert!(retrieved.is_some());
assert_eq!(retrieved, Some(data));
}
}
#[test]
fn property_cache_size_bounded() {
let max_size = 1000;
let mut cache = TileCache::new(max_size);
for i in 0..500 {
let coord = TileCoord::new(0, i, 0);
let data = vec![1u8; 10];
let _ = cache.put(coord, data, i as f64);
let stats = cache.stats();
assert!(stats.current_size <= max_size);
}
}
#[test]
fn property_lru_ordering() {
let mut cache = TileCache::new(100);
let coord1 = TileCoord::new(0, 0, 0);
let coord2 = TileCoord::new(0, 1, 0);
let coord3 = TileCoord::new(0, 2, 0);
cache.put(coord1, vec![1; 20], 0.0).expect("Put 1");
cache.put(coord2, vec![2; 20], 1.0).expect("Put 2");
cache.put(coord3, vec![3; 20], 2.0).expect("Put 3");
cache.get(&coord1, 3.0);
let coord4 = TileCoord::new(0, 3, 0);
cache.put(coord4, vec![4; 50], 4.0).expect("Put 4");
assert!(cache.contains(&coord1));
}
#[test]
fn property_clear_empties_cache() {
let mut cache = TileCache::new(1000);
for i in 0..50 {
let coord = TileCoord::new(0, i, 0);
cache.put(coord, vec![1u8; 10], i as f64).expect("Put");
}
cache.clear();
let stats = cache.stats();
assert_eq!(stats.entry_count, 0);
assert_eq!(stats.current_size, 0);
}
}
mod image_processing_properties {
use super::*;
#[test]
fn property_brightness_commutative() {
let mut data1 = vec![100, 100, 100, 255];
let mut data2 = vec![100, 100, 100, 255];
ImageProcessor::adjust_brightness(&mut data1, 20);
ImageProcessor::adjust_brightness(&mut data1, 30);
ImageProcessor::adjust_brightness(&mut data2, 50);
assert_eq!(data1[0], data2[0]);
}
#[test]
fn property_invert_inverse() {
for val in 0..=255 {
let mut data = vec![val, val, val, 255];
let original = data.clone();
ImageProcessor::invert(&mut data);
ImageProcessor::invert(&mut data);
assert_eq!(data, original);
}
}
#[test]
fn property_grayscale_preserves_luminance() {
for gray_val in 0..=255 {
let mut data = vec![gray_val, gray_val, gray_val, 255];
ImageProcessor::to_grayscale(&mut data);
assert_eq!(data[0], gray_val);
assert_eq!(data[1], gray_val);
assert_eq!(data[2], gray_val);
}
}
#[test]
fn property_histogram_counts_all_pixels() {
let width = 10;
let height = 10;
let pixel_count = width * height;
let mut data = vec![];
for _ in 0..pixel_count {
data.extend_from_slice(&[128, 64, 192, 255]);
}
let hist = Histogram::from_rgba(&data, width, height).expect("Histogram");
let mut total = 0;
for count in hist.red.iter() {
total += count;
}
assert_eq!(total, pixel_count);
}
#[test]
fn property_color_conversion_preserves_black_white() {
let black = Rgb::new(0, 0, 0);
let white = Rgb::new(255, 255, 255);
let black_hsv = black.to_hsv();
let black_back = black_hsv.to_rgb();
assert_eq!(black_back.r, 0);
assert_eq!(black_back.g, 0);
assert_eq!(black_back.b, 0);
let white_hsv = white.to_hsv();
let white_back = white_hsv.to_rgb();
assert_eq!(white_back.r, 255);
assert_eq!(white_back.g, 255);
assert_eq!(white_back.b, 255);
}
}
mod compression_properties {
use super::*;
#[test]
fn property_compression_roundtrip() {
let test_cases = vec![
vec![],
vec![0],
vec![255],
vec![1, 2, 3, 4, 5],
vec![100; 100],
(0..=255).collect::<Vec<u8>>(),
];
for original in test_cases {
let compressed = RleCompressor::compress(&original);
let decompressed = RleCompressor::decompress(&compressed).expect("Decompress");
assert_eq!(original, decompressed);
}
}
#[test]
fn property_delta_compression_roundtrip() {
let test_cases = vec![
vec![],
vec![0],
vec![100; 50],
(0..100).collect::<Vec<u8>>(),
(0..100).rev().collect::<Vec<u8>>(),
];
for original in test_cases {
let compressed = DeltaCompressor::compress(&original);
let decompressed = DeltaCompressor::decompress(&compressed).expect("Decompress");
assert_eq!(original, decompressed);
}
}
#[test]
fn property_compression_reduces_repetitive_data() {
let repetitive = vec![42u8; 1000];
let compressed = RleCompressor::compress(&repetitive);
assert!(compressed.len() < 100);
}
#[test]
fn property_huffman_roundtrip() {
let test_cases = vec![
vec![1; 100],
vec![1, 2, 1, 2, 1, 2],
(0..=255).cycle().take(1000).collect::<Vec<u8>>(),
];
for original in test_cases {
if original.is_empty() {
continue;
}
let compressed = HuffmanCompressor::compress(&original);
assert!(
!compressed.is_empty(),
"Compression should produce output for {:?}",
original
);
}
}
}
mod viewport_properties {
use super::*;
#[test]
fn property_transform_inverse() {
let transforms = vec![
ViewportTransform::identity(),
ViewportTransform::translate(10.0, 20.0),
ViewportTransform::scale(2.0, 2.0),
ViewportTransform::new(1.5, 0.0, 0.0, 1.5, 50.0, 50.0),
];
for transform in transforms {
for x in (0..100).step_by(10) {
for y in (0..100).step_by(10) {
let (tx, ty) = transform.transform_point(x as f64, y as f64);
let (ix, iy) = transform.inverse_transform_point(tx, ty);
assert!((ix - x as f64).abs() < 0.01);
assert!((iy - y as f64).abs() < 0.01);
}
}
}
}
#[test]
fn property_identity_transform_unchanged() {
let identity = ViewportTransform::identity();
for x in 0..100 {
for y in 0..100 {
let (tx, ty) = identity.transform_point(x as f64, y as f64);
assert_eq!(tx, x as f64);
assert_eq!(ty, y as f64);
}
}
}
#[test]
fn property_translation_vector_addition() {
for dx in (0..100).step_by(10) {
for dy in (0..100).step_by(10) {
let transform = ViewportTransform::translate(dx as f64, dy as f64);
for x in 0..10 {
for y in 0..10 {
let (tx, ty) = transform.transform_point(x as f64, y as f64);
assert_eq!(tx, x as f64 + dx as f64);
assert_eq!(ty, y as f64 + dy as f64);
}
}
}
}
}
#[test]
fn property_scale_multiplies_coordinates() {
for scale in [0.5, 1.0, 2.0, 3.0] {
let transform = ViewportTransform::scale(scale, scale);
for x in 0..10 {
for y in 0..10 {
let (tx, ty) = transform.transform_point(x as f64, y as f64);
assert!((tx - x as f64 * scale).abs() < 0.01);
assert!((ty - y as f64 * scale).abs() < 0.01);
}
}
}
}
#[test]
fn property_transform_composition_associative() {
let t1 = ViewportTransform::translate(10.0, 20.0);
let t2 = ViewportTransform::scale(2.0, 2.0);
let t3 = ViewportTransform::translate(5.0, 5.0);
let left = t1.compose(&t2).compose(&t3);
let right = t1.compose(&t2.compose(&t3));
for x in 0..10 {
for y in 0..10 {
let (lx, ly) = left.transform_point(x as f64, y as f64);
let (rx, ry) = right.transform_point(x as f64, y as f64);
assert!((lx - rx).abs() < 0.01);
assert!((ly - ry).abs() < 0.01);
}
}
}
}
mod profiler_properties {
use super::*;
#[test]
fn property_record_increases_count() {
let mut profiler = Profiler::new();
for i in 0..100 {
profiler.record("test", 10.0);
let stats = profiler.counter_stats("test").expect("Stats");
assert_eq!(stats.count, i + 1);
}
}
#[test]
fn property_total_time_sum_of_records() {
let mut profiler = Profiler::new();
let mut expected_total = 0.0;
for i in 0..50 {
let duration = i as f64;
profiler.record("test", duration);
expected_total += duration;
}
let stats = profiler.counter_stats("test").expect("Stats");
assert!((stats.total_time_ms - expected_total).abs() < 0.01);
}
#[test]
fn property_average_equals_total_divided_by_count() {
let mut profiler = Profiler::new();
for i in 0..50 {
profiler.record("test", (i * 2) as f64);
}
let stats = profiler.counter_stats("test").expect("Stats");
let expected_avg = stats.total_time_ms / stats.count as f64;
assert!((stats.average_ms - expected_avg).abs() < 0.01);
}
#[test]
fn property_reset_clears_all_counters() {
let mut profiler = Profiler::new();
for i in 0..10 {
let name = format!("counter_{}", i);
profiler.record(&name, 10.0);
}
profiler.reset();
let summary = profiler.summary();
assert_eq!(summary.counters.len(), 0);
}
}
mod streaming_properties {
use super::*;
#[test]
fn property_quality_increases_with_bandwidth() {
let mut adapter = QualityAdapter::new();
let bandwidths = [100_000.0, 500_000.0, 2_000_000.0, 5_000_000.0];
let mut last_quality = StreamingQuality::Low;
for (i, &bw) in bandwidths.iter().enumerate() {
for j in 0..3 {
adapter.update_bandwidth(bw, (i * 3 + j) as f64);
}
let current_quality = adapter.current_quality();
assert!(current_quality as u8 >= last_quality as u8);
last_quality = current_quality;
}
}
#[test]
fn property_bandwidth_estimate_increases_with_speed() {
let mut estimator = BandwidthEstimator::new();
estimator.record_download(10000, 1.0);
let bw1 = estimator.estimate();
estimator.record_download(100000, 1.0);
let bw2 = estimator.estimate();
estimator.record_download(1000000, 1.0);
let bw3 = estimator.estimate();
assert!(bw2 > bw1);
assert!(bw3 > bw2);
}
}
}