943 lines
27 KiB
Rust
943 lines
27 KiB
Rust
//! Stress and edge case tests for ruQu coherence gate
|
|
//!
|
|
//! Tests for high throughput syndrome streaming, memory pressure (64KB budget),
|
|
//! rapid decision cycling, and error recovery scenarios.
|
|
|
|
use ruqu::filters::{
|
|
EvidenceAccumulator, EvidenceConfig, EvidenceFilter, FilterConfig, FilterPipeline, ShiftConfig,
|
|
ShiftFilter, StructuralConfig, StructuralFilter, SystemState, Verdict,
|
|
};
|
|
use ruqu::syndrome::{DetectorBitmap, SyndromeBuffer, SyndromeDelta, SyndromeRound};
|
|
use ruqu::tile::{
|
|
GateDecision, GateThresholds, PatchGraph, ReceiptLog, SyndromeDelta as TileSyndromeDelta,
|
|
TileReport, TileZero, WorkerTile, MAX_PATCH_EDGES, MAX_PATCH_VERTICES, SYNDROME_BUFFER_DEPTH,
|
|
};
|
|
use ruqu::{TILE_MEMORY_BUDGET, WORKER_TILE_COUNT};
|
|
|
|
use std::time::Instant;
|
|
|
|
// ============================================================================
|
|
// High Throughput Syndrome Streaming Tests
|
|
// ============================================================================
|
|
|
|
mod throughput_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_syndrome_stream_10k_rounds() {
|
|
let mut buffer = SyndromeBuffer::new(1024);
|
|
|
|
for i in 0..10_000 {
|
|
let mut detectors = DetectorBitmap::new(64);
|
|
if i % 100 == 0 {
|
|
detectors.set(i as usize % 64, true);
|
|
}
|
|
let round = SyndromeRound::new(i, i, i * 1_000, detectors, 0);
|
|
buffer.push(round);
|
|
}
|
|
|
|
// Buffer should still function correctly
|
|
assert_eq!(buffer.len(), 1024);
|
|
assert!(buffer.get(9_999).is_some());
|
|
assert!(buffer.get(8_975).is_none()); // Evicted
|
|
}
|
|
|
|
#[test]
|
|
fn test_syndrome_stream_100k_rounds() {
|
|
let mut buffer = SyndromeBuffer::new(1024);
|
|
|
|
let start = Instant::now();
|
|
|
|
for i in 0..100_000u64 {
|
|
let mut detectors = DetectorBitmap::new(256);
|
|
if i % 10 == 0 {
|
|
for j in 0..(i % 10) as usize {
|
|
detectors.set(j, true);
|
|
}
|
|
}
|
|
let round = SyndromeRound::new(i, i, i * 1_000, detectors, 0);
|
|
buffer.push(round);
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
// Performance sanity check - should complete in reasonable time
|
|
assert!(
|
|
duration.as_millis() < 5_000,
|
|
"100k rounds took too long: {:?}",
|
|
duration
|
|
);
|
|
|
|
// Data integrity
|
|
assert_eq!(buffer.len(), 1024);
|
|
}
|
|
|
|
#[test]
|
|
fn test_worker_tile_high_throughput() {
|
|
let mut tile = WorkerTile::new(1);
|
|
|
|
let start = Instant::now();
|
|
|
|
for i in 0..10_000 {
|
|
let delta =
|
|
TileSyndromeDelta::new((i % 64) as u16, ((i + 1) % 64) as u16, (i % 256) as u16);
|
|
tile.tick(&delta);
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
assert_eq!(tile.tick, 10_000);
|
|
assert!(
|
|
duration.as_millis() < 5_000,
|
|
"10k ticks took too long: {:?}",
|
|
duration
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tilezero_high_report_throughput() {
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..1_000 {
|
|
let reports: Vec<TileReport> = (1..=50)
|
|
.map(|i| {
|
|
let mut report = TileReport::new(i);
|
|
report.local_cut = 10.0;
|
|
report.shift_score = 0.1;
|
|
report.e_value = 200.0;
|
|
report
|
|
})
|
|
.collect();
|
|
|
|
tilezero.merge_reports(reports);
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
assert_eq!(tilezero.receipt_log.len(), 1_000);
|
|
assert!(
|
|
duration.as_millis() < 5_000,
|
|
"1000 merges took too long: {:?}",
|
|
duration
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_bitmap_operations_throughput() {
|
|
let mut a = DetectorBitmap::new(1024);
|
|
let mut b = DetectorBitmap::new(1024);
|
|
|
|
// Setup
|
|
for i in (0..1024).step_by(2) {
|
|
a.set(i, true);
|
|
}
|
|
for i in (1..1024).step_by(2) {
|
|
b.set(i, true);
|
|
}
|
|
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..100_000 {
|
|
let _ = a.xor(&b);
|
|
let _ = a.and(&b);
|
|
let _ = a.or(&b);
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
// 300k bitmap operations should be fast (SIMD-like)
|
|
assert!(
|
|
duration.as_millis() < 2_000,
|
|
"Bitmap ops took too long: {:?}",
|
|
duration
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_popcount_throughput() {
|
|
let mut bitmap = DetectorBitmap::new(1024);
|
|
|
|
for i in (0..1024).step_by(3) {
|
|
bitmap.set(i, true);
|
|
}
|
|
|
|
let start = Instant::now();
|
|
|
|
let mut total = 0usize;
|
|
for _ in 0..1_000_000 {
|
|
total += bitmap.popcount();
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
// 1M popcounts should be very fast (hardware instruction)
|
|
assert!(
|
|
duration.as_millis() < 1_000,
|
|
"Popcount ops took too long: {:?}",
|
|
duration
|
|
);
|
|
assert!(total > 0); // Prevent optimization
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Memory Pressure Tests (64KB Budget)
|
|
// ============================================================================
|
|
|
|
mod memory_pressure_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_worker_tile_memory_budget() {
|
|
let size = WorkerTile::memory_size();
|
|
|
|
// Target is 64KB per tile, allow up to 128KB
|
|
assert!(
|
|
size <= TILE_MEMORY_BUDGET * 2,
|
|
"WorkerTile exceeds 128KB budget: {} bytes",
|
|
size
|
|
);
|
|
|
|
// Log actual size for monitoring
|
|
println!(
|
|
"WorkerTile memory: {} bytes ({:.1}% of 64KB)",
|
|
size,
|
|
(size as f64 / 65536.0) * 100.0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_patch_graph_memory_budget() {
|
|
let size = PatchGraph::memory_size();
|
|
|
|
// PatchGraph should be ~32KB
|
|
assert!(size <= 65536, "PatchGraph exceeds 64KB: {} bytes", size);
|
|
|
|
println!("PatchGraph memory: {} bytes", size);
|
|
}
|
|
|
|
#[test]
|
|
fn test_syndrome_buffer_memory_budget() {
|
|
let size = ruqu::tile::SyndromBuffer::memory_size();
|
|
|
|
// SyndromBuffer should be ~16KB
|
|
assert!(size <= 32768, "SyndromBuffer exceeds 32KB: {} bytes", size);
|
|
|
|
println!("SyndromBuffer memory: {} bytes", size);
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_tiles_memory() {
|
|
// Simulate 256-tile fabric memory
|
|
let tile_size = WorkerTile::memory_size();
|
|
let total_memory = tile_size * 255; // 255 worker tiles
|
|
|
|
// Total should be reasonable (target ~16MB for all tiles)
|
|
let mb = total_memory / (1024 * 1024);
|
|
println!("Total fabric memory (255 tiles): {} MB", mb);
|
|
|
|
assert!(mb < 64, "Total fabric memory exceeds 64MB: {} MB", mb);
|
|
}
|
|
|
|
#[test]
|
|
fn test_patch_graph_at_capacity() {
|
|
let mut graph = PatchGraph::new();
|
|
|
|
// Fill to edge capacity
|
|
let mut edge_count = 0;
|
|
for v1 in 0..16 {
|
|
for v2 in (v1 + 1)..16 {
|
|
if graph.add_edge(v1, v2, 100).is_some() {
|
|
edge_count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Should handle many edges
|
|
assert!(edge_count > 0);
|
|
assert_eq!(graph.num_edges as usize, edge_count);
|
|
}
|
|
|
|
#[test]
|
|
fn test_patch_graph_vertex_limit() {
|
|
let mut graph = PatchGraph::new();
|
|
|
|
// Try to use vertices up to limit
|
|
for i in 0..(MAX_PATCH_VERTICES - 1) {
|
|
let v1 = i as u16;
|
|
let v2 = (i + 1) as u16;
|
|
if v2 < MAX_PATCH_VERTICES as u16 {
|
|
graph.add_edge(v1, v2, 100);
|
|
}
|
|
}
|
|
|
|
assert!(graph.num_vertices <= MAX_PATCH_VERTICES as u16);
|
|
}
|
|
|
|
#[test]
|
|
fn test_syndrome_buffer_at_depth() {
|
|
let mut buffer = ruqu::tile::SyndromBuffer::new();
|
|
|
|
// Fill to depth
|
|
for i in 0..SYNDROME_BUFFER_DEPTH as u32 {
|
|
let entry = ruqu::tile::SyndromeEntry {
|
|
round: i,
|
|
syndrome: [i as u8; 8],
|
|
flags: 0,
|
|
};
|
|
buffer.append(entry);
|
|
}
|
|
|
|
assert_eq!(buffer.count as usize, SYNDROME_BUFFER_DEPTH);
|
|
|
|
// Overflow
|
|
let entry = ruqu::tile::SyndromeEntry {
|
|
round: SYNDROME_BUFFER_DEPTH as u32,
|
|
syndrome: [0; 8],
|
|
flags: 0,
|
|
};
|
|
buffer.append(entry);
|
|
|
|
assert_eq!(buffer.count as usize, SYNDROME_BUFFER_DEPTH);
|
|
}
|
|
|
|
#[test]
|
|
fn test_receipt_log_growth() {
|
|
let mut log = ReceiptLog::new();
|
|
|
|
// Log many receipts
|
|
for i in 0..10_000 {
|
|
log.append(GateDecision::Permit, i, i * 1_000, [0u8; 32]);
|
|
}
|
|
|
|
assert_eq!(log.len(), 10_000);
|
|
|
|
// Should still be searchable
|
|
assert!(log.get(5_000).is_some());
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Rapid Decision Cycling Tests
|
|
// ============================================================================
|
|
|
|
mod rapid_decision_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_rapid_permit_deny_cycling() {
|
|
let thresholds = GateThresholds {
|
|
structural_min_cut: 5.0,
|
|
..Default::default()
|
|
};
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
for i in 0..1_000 {
|
|
let cut_value = if i % 2 == 0 { 10.0 } else { 1.0 };
|
|
|
|
let reports: Vec<TileReport> = (1..=5)
|
|
.map(|j| {
|
|
let mut report = TileReport::new(j);
|
|
report.local_cut = cut_value;
|
|
report.shift_score = 0.1;
|
|
report.e_value = 200.0;
|
|
report
|
|
})
|
|
.collect();
|
|
|
|
let decision = tilezero.merge_reports(reports);
|
|
|
|
if cut_value < 5.0 {
|
|
assert_eq!(decision, GateDecision::Deny);
|
|
} else {
|
|
assert_eq!(decision, GateDecision::Permit);
|
|
}
|
|
}
|
|
|
|
assert_eq!(tilezero.receipt_log.len(), 1_000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_rapid_filter_evaluation() {
|
|
let config = FilterConfig {
|
|
structural: StructuralConfig {
|
|
threshold: 2.0,
|
|
use_subpolynomial: false,
|
|
..Default::default()
|
|
},
|
|
shift: ShiftConfig {
|
|
threshold: 0.5,
|
|
..Default::default()
|
|
},
|
|
evidence: EvidenceConfig {
|
|
tau_permit: 10.0,
|
|
tau_deny: 0.1,
|
|
..Default::default()
|
|
},
|
|
};
|
|
|
|
let mut pipeline = FilterPipeline::new(config);
|
|
pipeline.structural_mut().insert_edge(1, 2, 3.0).unwrap();
|
|
pipeline.structural_mut().insert_edge(2, 3, 3.0).unwrap();
|
|
|
|
let state = SystemState::new(3);
|
|
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..10_000 {
|
|
let _ = pipeline.evaluate(&state);
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
// 10k evaluations should be fast
|
|
assert!(
|
|
duration.as_millis() < 5_000,
|
|
"10k evaluations took too long: {:?}",
|
|
duration
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_evidence_rapid_accumulation() {
|
|
let mut acc = EvidenceAccumulator::new();
|
|
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..100_000 {
|
|
acc.update(1.1);
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
// 100k updates should be fast
|
|
assert!(
|
|
duration.as_millis() < 1_000,
|
|
"100k evidence updates took too long: {:?}",
|
|
duration
|
|
);
|
|
|
|
// E-value should be very high
|
|
assert!(acc.e_value() > 1e10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_shift_filter_rapid_updates() {
|
|
let mut filter = ShiftFilter::new(0.5, 100);
|
|
|
|
let start = Instant::now();
|
|
|
|
for i in 0..100_000 {
|
|
filter.update(i % 64, (i as f64) % 10.0);
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
assert!(
|
|
duration.as_millis() < 2_000,
|
|
"100k shift updates took too long: {:?}",
|
|
duration
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_decision_state_transitions() {
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
let mut last_decision = GateDecision::Permit;
|
|
let mut transitions = 0;
|
|
|
|
for i in 0..1_000 {
|
|
// Vary parameters to cause state changes
|
|
let cut_value = 5.0 + (i as f64).sin() * 10.0;
|
|
let shift_score = 0.3 + (i as f64).cos().abs() * 0.4;
|
|
|
|
let reports: Vec<TileReport> = (1..=5)
|
|
.map(|j| {
|
|
let mut report = TileReport::new(j);
|
|
report.local_cut = cut_value.max(0.1);
|
|
report.shift_score = shift_score;
|
|
report.e_value = 200.0;
|
|
report
|
|
})
|
|
.collect();
|
|
|
|
let decision = tilezero.merge_reports(reports);
|
|
|
|
if decision != last_decision {
|
|
transitions += 1;
|
|
last_decision = decision;
|
|
}
|
|
}
|
|
|
|
// Should have some state transitions
|
|
println!("Decision state transitions: {}", transitions);
|
|
assert!(transitions > 0);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Error Recovery Tests
|
|
// ============================================================================
|
|
|
|
mod error_recovery_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_structural_filter_edge_operation_errors() {
|
|
let mut filter = StructuralFilter::new(5.0);
|
|
|
|
// Duplicate edge
|
|
filter.insert_edge(1, 2, 1.0).unwrap();
|
|
let result = filter.insert_edge(1, 2, 1.0);
|
|
assert!(result.is_err());
|
|
|
|
// Delete nonexistent
|
|
let result = filter.delete_edge(5, 6);
|
|
assert!(result.is_err());
|
|
|
|
// Filter should still work
|
|
let state = SystemState::new(2);
|
|
let eval = filter.evaluate(&state);
|
|
assert!(eval.compute_time_us < 1_000_000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_patch_graph_recovery_from_bad_operations() {
|
|
let mut graph = PatchGraph::new();
|
|
|
|
// Add valid edges
|
|
graph.add_edge(0, 1, 100);
|
|
graph.add_edge(1, 2, 100);
|
|
|
|
// Try invalid operations
|
|
let _ = graph.add_edge(0, 0, 100); // Self-loop
|
|
let _ = graph.add_edge(MAX_PATCH_VERTICES as u16, 0, 100); // Out of bounds
|
|
let _ = graph.remove_edge(5, 6); // Nonexistent
|
|
|
|
// Graph should still be valid
|
|
assert_eq!(graph.num_edges, 2);
|
|
assert!(graph.estimate_local_cut() > 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_buffer_recovery_from_rapid_operations() {
|
|
let mut buffer = SyndromeBuffer::new(100);
|
|
|
|
// Rapid push/clear cycles
|
|
for cycle in 0..100 {
|
|
for i in 0..50 {
|
|
let round = SyndromeRound::new(
|
|
cycle * 50 + i,
|
|
cycle * 50 + i,
|
|
(cycle * 50 + i) * 1_000,
|
|
DetectorBitmap::new(64),
|
|
0,
|
|
);
|
|
buffer.push(round);
|
|
}
|
|
|
|
if cycle % 10 == 0 {
|
|
buffer.clear();
|
|
}
|
|
}
|
|
|
|
// Buffer should be valid
|
|
assert!(buffer.len() <= 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_worker_tile_reset_recovery() {
|
|
let mut tile = WorkerTile::new(1);
|
|
|
|
// Build up state
|
|
for _ in 0..100 {
|
|
let delta = TileSyndromeDelta::new(0, 1, 100);
|
|
tile.tick(&delta);
|
|
}
|
|
|
|
// Add graph structure
|
|
tile.patch_graph.add_edge(0, 1, 100);
|
|
tile.patch_graph.add_edge(1, 2, 100);
|
|
|
|
// Reset
|
|
tile.reset();
|
|
|
|
// Should be clean
|
|
assert_eq!(tile.tick, 0);
|
|
assert_eq!(tile.patch_graph.num_edges, 0);
|
|
assert_eq!(tile.syndrome_buffer.count, 0);
|
|
|
|
// Should work again
|
|
let delta = TileSyndromeDelta::new(0, 1, 50);
|
|
let report = tile.tick(&delta);
|
|
assert_eq!(report.tick, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_filter_pipeline_reset_recovery() {
|
|
let config = FilterConfig::default();
|
|
let mut pipeline = FilterPipeline::new(config);
|
|
|
|
// Build up state
|
|
for _ in 0..100 {
|
|
pipeline.shift_mut().update(0, 1.0);
|
|
pipeline.evidence_mut().update(2.0);
|
|
}
|
|
|
|
// Reset
|
|
pipeline.reset();
|
|
|
|
// Evidence should be back to neutral
|
|
let state = SystemState::new(10);
|
|
let result = pipeline.evaluate(&state);
|
|
assert!((result.evidence.e_value - 1.0).abs() < 0.5);
|
|
}
|
|
|
|
#[test]
|
|
fn test_evidence_overflow_protection() {
|
|
let mut acc = EvidenceAccumulator::new();
|
|
|
|
// Try to overflow with extreme values
|
|
for _ in 0..1000 {
|
|
acc.update(1e100); // Very large (will be clamped)
|
|
}
|
|
|
|
// Should not panic or be NaN/Inf
|
|
assert!(acc.e_value().is_finite());
|
|
|
|
// Reset should work
|
|
acc.reset();
|
|
assert_eq!(acc.e_value(), 1.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_evidence_underflow_protection() {
|
|
let mut acc = EvidenceAccumulator::new();
|
|
|
|
// Try to underflow with tiny values
|
|
for _ in 0..1000 {
|
|
acc.update(1e-100); // Very small (will be clamped)
|
|
}
|
|
|
|
// Should not panic or be NaN/Inf
|
|
assert!(acc.e_value().is_finite());
|
|
assert!(acc.e_value() >= 0.0);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Concurrent-Style Stress Tests (Sequential Simulation)
|
|
// ============================================================================
|
|
|
|
mod concurrent_stress_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_multiple_workers_same_syndrome_pattern() {
|
|
let mut workers: Vec<WorkerTile> = (1..=10).map(WorkerTile::new).collect();
|
|
|
|
// All workers process same syndrome pattern
|
|
for round in 0..100 {
|
|
let delta = TileSyndromeDelta::new(
|
|
(round % 64) as u16,
|
|
((round + 1) % 64) as u16,
|
|
(round % 256) as u16,
|
|
);
|
|
|
|
for worker in &mut workers {
|
|
worker.tick(&delta);
|
|
}
|
|
}
|
|
|
|
// All workers should be in sync
|
|
for worker in &workers {
|
|
assert_eq!(worker.tick, 100);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_workers_different_patterns() {
|
|
let mut workers: Vec<WorkerTile> = (1..=50).map(WorkerTile::new).collect();
|
|
|
|
// Each worker gets unique pattern
|
|
for round in 0..100 {
|
|
for (i, worker) in workers.iter_mut().enumerate() {
|
|
let delta = TileSyndromeDelta::new(
|
|
((round + i) % 64) as u16,
|
|
((round + i + 1) % 64) as u16,
|
|
((round + i) % 256) as u16,
|
|
);
|
|
worker.tick(&delta);
|
|
}
|
|
}
|
|
|
|
// All workers should have processed 100 rounds
|
|
for worker in &workers {
|
|
assert_eq!(worker.tick, 100);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_tilezero_varying_report_counts() {
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
// Vary the number of reports each cycle
|
|
for i in 0..100 {
|
|
let report_count = 1 + (i % 20);
|
|
|
|
let reports: Vec<TileReport> = (1..=report_count as u8)
|
|
.map(|j| {
|
|
let mut report = TileReport::new(j);
|
|
report.local_cut = 10.0;
|
|
report.shift_score = 0.1;
|
|
report.e_value = 200.0;
|
|
report
|
|
})
|
|
.collect();
|
|
|
|
tilezero.merge_reports(reports);
|
|
}
|
|
|
|
assert_eq!(tilezero.receipt_log.len(), 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_interleaved_operations() {
|
|
let mut buffer = SyndromeBuffer::new(100);
|
|
let mut filter = ShiftFilter::new(0.5, 100);
|
|
let mut evidence = EvidenceAccumulator::new();
|
|
|
|
// Interleave different operations
|
|
for i in 0..1_000 {
|
|
// Buffer operation
|
|
let round = SyndromeRound::new(i, i, i * 1_000, DetectorBitmap::new(64), 0);
|
|
buffer.push(round);
|
|
|
|
// Filter operation
|
|
filter.update(i as usize % 64, (i as f64) % 10.0);
|
|
|
|
// Evidence operation
|
|
evidence.update(1.0 + (i as f64 % 10.0) / 100.0);
|
|
|
|
// Occasional window access
|
|
if i % 100 == 0 {
|
|
let _ = buffer.window(10);
|
|
}
|
|
}
|
|
|
|
// All should be functional
|
|
assert_eq!(buffer.len(), 100);
|
|
assert!(evidence.e_value() > 1.0);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Boundary Condition Tests
|
|
// ============================================================================
|
|
|
|
mod boundary_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_empty_state_handling() {
|
|
// Empty filter pipeline
|
|
let config = FilterConfig::default();
|
|
let pipeline = FilterPipeline::new(config);
|
|
let state = SystemState::new(0);
|
|
let result = pipeline.evaluate(&state);
|
|
assert!(result.verdict.is_some());
|
|
|
|
// Empty tilezero
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
let decision = tilezero.merge_reports(vec![]);
|
|
// Empty reports should produce some decision
|
|
assert!(decision == GateDecision::Permit || decision == GateDecision::Defer);
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_element_handling() {
|
|
// Single round buffer
|
|
let mut buffer = SyndromeBuffer::new(1);
|
|
buffer.push(SyndromeRound::new(0, 0, 0, DetectorBitmap::new(64), 0));
|
|
assert_eq!(buffer.len(), 1);
|
|
assert_eq!(buffer.window(1).len(), 1);
|
|
|
|
// Single bit bitmap
|
|
let mut bitmap = DetectorBitmap::new(1);
|
|
bitmap.set(0, true);
|
|
assert_eq!(bitmap.fired_count(), 1);
|
|
|
|
// Single report
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
let mut report = TileReport::new(1);
|
|
report.local_cut = 10.0;
|
|
report.shift_score = 0.1;
|
|
report.e_value = 200.0;
|
|
let decision = tilezero.merge_reports(vec![report]);
|
|
assert_eq!(decision, GateDecision::Permit);
|
|
}
|
|
|
|
#[test]
|
|
fn test_maximum_values() {
|
|
// Max detectors
|
|
let mut bitmap = DetectorBitmap::new(1024);
|
|
for i in 0..1024 {
|
|
bitmap.set(i, true);
|
|
}
|
|
assert_eq!(bitmap.fired_count(), 1024);
|
|
|
|
// Max tile ID
|
|
let tile = WorkerTile::new(255);
|
|
assert_eq!(tile.tile_id, 255);
|
|
|
|
// Very high e-value
|
|
let mut evidence = EvidenceAccumulator::new();
|
|
for _ in 0..100 {
|
|
evidence.update(10.0);
|
|
}
|
|
assert!(evidence.e_value().is_finite());
|
|
}
|
|
|
|
#[test]
|
|
fn test_minimum_values() {
|
|
// Min detector count
|
|
let bitmap = DetectorBitmap::new(0);
|
|
assert_eq!(bitmap.fired_count(), 0);
|
|
|
|
// Very low e-value
|
|
let mut evidence = EvidenceAccumulator::new();
|
|
for _ in 0..100 {
|
|
evidence.update(0.1);
|
|
}
|
|
let e = evidence.e_value();
|
|
assert!(e.is_finite());
|
|
assert!(e >= 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_threshold_boundaries() {
|
|
let thresholds = GateThresholds {
|
|
structural_min_cut: 5.0,
|
|
shift_max: 0.5,
|
|
tau_deny: 0.01,
|
|
tau_permit: 100.0,
|
|
permit_ttl_ns: 4_000_000,
|
|
};
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
// Exactly at threshold
|
|
let mut report = TileReport::new(1);
|
|
report.local_cut = 5.0; // Exactly at threshold
|
|
report.shift_score = 0.5; // Exactly at threshold
|
|
report.e_value = 100.0; // Exactly at threshold
|
|
|
|
let decision = tilezero.merge_reports(vec![report]);
|
|
|
|
// At threshold behavior
|
|
assert!(decision == GateDecision::Permit || decision == GateDecision::Defer);
|
|
}
|
|
|
|
#[test]
|
|
fn test_just_below_thresholds() {
|
|
let thresholds = GateThresholds {
|
|
structural_min_cut: 5.0,
|
|
shift_max: 0.5,
|
|
tau_deny: 0.01,
|
|
tau_permit: 100.0,
|
|
permit_ttl_ns: 4_000_000,
|
|
};
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
// Just below structural threshold
|
|
let mut report = TileReport::new(1);
|
|
report.local_cut = 4.99;
|
|
report.shift_score = 0.1;
|
|
report.e_value = 200.0;
|
|
|
|
let decision = tilezero.merge_reports(vec![report]);
|
|
assert_eq!(decision, GateDecision::Deny);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Proptest Stress Tests
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod proptest_stress {
|
|
use super::*;
|
|
use proptest::prelude::*;
|
|
|
|
proptest! {
|
|
#![proptest_config(ProptestConfig::with_cases(20))]
|
|
|
|
#[test]
|
|
fn prop_buffer_survives_random_operations(
|
|
pushes in prop::collection::vec(0u64..10000, 100..1000),
|
|
capacity in 10usize..200
|
|
) {
|
|
let mut buffer = SyndromeBuffer::new(capacity);
|
|
|
|
for round_id in pushes {
|
|
let round = SyndromeRound::new(round_id, round_id, round_id * 1000, DetectorBitmap::new(64), 0);
|
|
buffer.push(round);
|
|
}
|
|
|
|
// Buffer should be valid
|
|
prop_assert!(buffer.len() <= capacity);
|
|
prop_assert!(!buffer.statistics().avg_firing_rate.is_nan());
|
|
}
|
|
|
|
#[test]
|
|
fn prop_worker_survives_random_deltas(
|
|
syndromes in prop::collection::vec((0u16..64, 0u16..64, 0u16..256), 100..500)
|
|
) {
|
|
let mut worker = WorkerTile::new(1);
|
|
|
|
for (src, tgt, val) in syndromes {
|
|
let delta = TileSyndromeDelta::new(src, tgt.max(1), val);
|
|
worker.tick(&delta);
|
|
}
|
|
|
|
// Worker should be valid
|
|
prop_assert!(worker.tick > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn prop_tilezero_survives_random_reports(
|
|
report_values in prop::collection::vec(
|
|
(0.0f64..20.0, 0.0f64..1.0, 0.01f64..500.0),
|
|
1..50
|
|
)
|
|
) {
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
let reports: Vec<TileReport> = report_values
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, (cut, shift, e_val))| {
|
|
let mut report = TileReport::new((i + 1) as u8);
|
|
report.local_cut = *cut;
|
|
report.shift_score = *shift;
|
|
report.e_value = *e_val;
|
|
report
|
|
})
|
|
.collect();
|
|
|
|
let decision = tilezero.merge_reports(reports);
|
|
|
|
// Decision should be valid
|
|
prop_assert!(matches!(decision, GateDecision::Permit | GateDecision::Defer | GateDecision::Deny));
|
|
}
|
|
}
|
|
}
|