Files

1046 lines
29 KiB
Rust

//! Tile architecture tests for ruQu coherence gate
//!
//! Tests for the 256-tile WASM fabric:
//! - WorkerTile tick processing
//! - TileZero report merging
//! - Permit token issuance and verification
//! - 256-tile scaling
use ruqu::tile::{
Edge, EvidenceAccumulator, GateDecision, GateThresholds, LocalCutState, PatchGraph,
PermitToken, ReceiptLog, SyndromBuffer, SyndromeDelta, SyndromeEntry, TileReport, TileZero,
Vertex, WorkerTile, MAX_BOUNDARY_CANDIDATES, MAX_PATCH_EDGES, MAX_PATCH_VERTICES, NUM_WORKERS,
SYNDROME_BUFFER_DEPTH,
};
// ============================================================================
// WorkerTile Tick Processing Tests
// ============================================================================
mod worker_tile_tests {
use super::*;
#[test]
fn test_worker_tile_creation() {
let tile = WorkerTile::new(42);
assert_eq!(tile.tile_id, 42);
assert_eq!(tile.tick, 0);
assert_eq!(tile.generation, 0);
}
#[test]
fn test_worker_tile_tick_increments() {
let mut tile = WorkerTile::new(1);
let delta = SyndromeDelta::new(0, 1, 100);
tile.tick(&delta);
assert_eq!(tile.tick, 1);
for _ in 0..99 {
tile.tick(&delta);
}
assert_eq!(tile.tick, 100);
}
#[test]
fn test_worker_tile_tick_returns_report() {
let mut tile = WorkerTile::new(5);
let delta = SyndromeDelta::new(0, 1, 50);
let report = tile.tick(&delta);
assert_eq!(report.tile_id, 5);
assert_eq!(report.tick, 1);
assert!(report.status & TileReport::STATUS_VALID != 0);
}
#[test]
fn test_worker_tile_syndrome_updates_graph() {
let mut tile = WorkerTile::new(1);
// Edge addition delta
let delta = SyndromeDelta::edge_add(0, 1, 100);
tile.tick(&delta);
assert_eq!(tile.patch_graph.num_edges, 1);
assert_eq!(tile.patch_graph.num_vertices, 2);
}
#[test]
fn test_worker_tile_syndrome_buffer_populated() {
let mut tile = WorkerTile::new(1);
for i in 0..50 {
let delta = SyndromeDelta::new(0, 1, i as u16);
tile.tick(&delta);
}
assert_eq!(tile.syndrome_buffer.count, 50);
}
#[test]
fn test_worker_tile_evidence_accumulates() {
let mut tile = WorkerTile::new(1);
// Process multiple syndromes
for _ in 0..100 {
let delta = SyndromeDelta::new(0, 1, 50); // Low value = evidence for coherence
tile.tick(&delta);
}
// Evidence should have accumulated
assert!(tile.evidence.obs_count > 0);
}
#[test]
fn test_worker_tile_cut_state_updates() {
let mut tile = WorkerTile::new(1);
// Add graph structure
let delta = SyndromeDelta::edge_add(0, 1, 100);
tile.tick(&delta);
let delta = SyndromeDelta::edge_add(1, 2, 100);
tile.tick(&delta);
// Cut state should be computed
assert!(tile.local_cut_state.generation > 0);
}
#[test]
fn test_worker_tile_shift_score_computed() {
let mut tile = WorkerTile::new(1);
// Need enough syndrome history for shift computation
for i in 0..100 {
let delta = SyndromeDelta::new(0, 1, (i % 256) as u16);
tile.tick(&delta);
}
let delta = SyndromeDelta::new(0, 1, 50);
let report = tile.tick(&delta);
// Shift score should be computed (might be 0.0 if stable)
assert!(report.shift_score >= 0.0 && report.shift_score <= 1.0);
}
#[test]
fn test_worker_tile_reset() {
let mut tile = WorkerTile::new(1);
// Add state
for _ in 0..50 {
let delta = SyndromeDelta::new(0, 1, 100);
tile.tick(&delta);
}
assert!(tile.tick > 0);
// Reset
tile.reset();
assert_eq!(tile.tick, 0);
assert_eq!(tile.generation, 0);
assert_eq!(tile.syndrome_buffer.count, 0);
}
#[test]
fn test_worker_tile_boundary_moved_detection() {
let mut tile = WorkerTile::new(1);
// Build initial graph
tile.patch_graph.add_edge(0, 1, 100);
tile.patch_graph.add_edge(1, 2, 100);
tile.patch_graph.recompute_components();
tile.local_cut_state.update_from_graph(&tile.patch_graph);
// Significant change
tile.patch_graph.add_edge(2, 3, 1000);
tile.patch_graph.recompute_components();
tile.local_cut_state.update_from_graph(&tile.patch_graph);
// May or may not detect boundary movement depending on magnitude
// The flag is set based on relative change
assert!(tile.local_cut_state.generation > 0);
}
#[test]
fn test_worker_tile_memory_size() {
let size = WorkerTile::memory_size();
// Should be within reasonable bounds (target ~64KB, allow some margin)
assert!(size > 0);
assert!(size <= 131072); // 128KB max
}
}
// ============================================================================
// TileZero Report Merging Tests
// ============================================================================
mod tilezero_tests {
use super::*;
#[test]
fn test_tilezero_creation() {
let thresholds = GateThresholds::default();
let tilezero = TileZero::new(thresholds);
assert!(tilezero.receipt_log.is_empty());
}
#[test]
fn test_tilezero_merge_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_tilezero_merge_multiple_reports() {
let thresholds = GateThresholds::default();
let mut tilezero = TileZero::new(thresholds);
let reports: Vec<TileReport> = (1..=10)
.map(|i| {
let mut report = TileReport::new(i);
report.local_cut = 10.0 + i as f64;
report.shift_score = 0.1;
report.e_value = 200.0;
report
})
.collect();
let decision = tilezero.merge_reports(reports);
assert_eq!(decision, GateDecision::Permit);
}
#[test]
fn test_tilezero_merge_takes_min_cut() {
let thresholds = GateThresholds {
structural_min_cut: 8.0,
..Default::default()
};
let mut tilezero = TileZero::new(thresholds);
// One tile has low cut
let reports: Vec<TileReport> = (1..=5)
.map(|i| {
let mut report = TileReport::new(i);
report.local_cut = if i == 3 { 5.0 } else { 15.0 }; // Tile 3 has low cut
report.shift_score = 0.1;
report.e_value = 200.0;
report
})
.collect();
let decision = tilezero.merge_reports(reports);
// Should deny because minimum cut (5.0) < threshold (8.0)
assert_eq!(decision, GateDecision::Deny);
}
#[test]
fn test_tilezero_merge_takes_max_shift() {
let thresholds = GateThresholds {
structural_min_cut: 2.0,
shift_max: 0.5,
..Default::default()
};
let mut tilezero = TileZero::new(thresholds);
// One tile has high shift
let reports: Vec<TileReport> = (1..=5)
.map(|i| {
let mut report = TileReport::new(i);
report.local_cut = 10.0;
report.shift_score = if i == 3 { 0.8 } else { 0.1 }; // Tile 3 has high shift
report.e_value = 200.0;
report
})
.collect();
let decision = tilezero.merge_reports(reports);
// Should defer because max shift (0.8) >= threshold (0.5)
assert_eq!(decision, GateDecision::Defer);
}
#[test]
fn test_tilezero_aggregates_evidence() {
let thresholds = GateThresholds {
tau_permit: 50.0,
..Default::default()
};
let mut tilezero = TileZero::new(thresholds);
// Mix of evidence values
let reports: Vec<TileReport> = (1..=4)
.map(|i| {
let mut report = TileReport::new(i);
report.local_cut = 10.0;
report.shift_score = 0.1;
report.e_value = 100.0 * i as f64; // 100, 200, 300, 400
report
})
.collect();
let decision = tilezero.merge_reports(reports);
// Geometric mean of e-values should be above threshold
assert_eq!(decision, GateDecision::Permit);
}
#[test]
fn test_tilezero_empty_reports() {
let thresholds = GateThresholds::default();
let mut tilezero = TileZero::new(thresholds);
let decision = tilezero.merge_reports(vec![]);
// With no reports, should default to safe behavior
// (max cut = infinity, so passes structural)
assert!(decision == GateDecision::Permit || decision == GateDecision::Defer);
}
#[test]
fn test_tilezero_reports_accessor() {
let thresholds = GateThresholds::default();
let mut tilezero = TileZero::new(thresholds);
let reports: Vec<TileReport> = (1..=3)
.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);
assert_eq!(tilezero.reports().len(), 3);
}
}
// ============================================================================
// Permit Token Tests
// ============================================================================
mod permit_token_tests {
use super::*;
#[test]
fn test_permit_token_validity_within_ttl() {
let token = PermitToken {
decision: GateDecision::Permit,
sequence: 0,
timestamp: 1_000_000,
ttl_ns: 500_000,
witness_hash: [0u8; 32],
signature: [1u8; 64], // Non-zero placeholder
};
assert!(token.is_valid(1_000_000)); // At issuance
assert!(token.is_valid(1_200_000)); // Within TTL
assert!(token.is_valid(1_499_999)); // Just before expiry
}
#[test]
fn test_permit_token_validity_after_ttl() {
let token = PermitToken {
decision: GateDecision::Permit,
sequence: 0,
timestamp: 1_000_000,
ttl_ns: 500_000,
witness_hash: [0u8; 32],
signature: [1u8; 64], // Non-zero placeholder
};
assert!(!token.is_valid(1_500_001)); // Just after expiry
assert!(!token.is_valid(2_000_000)); // Well after expiry
}
#[test]
fn test_permit_token_deny_always_invalid() {
let token = PermitToken {
decision: GateDecision::Deny,
sequence: 0,
timestamp: 1_000_000,
ttl_ns: 500_000,
witness_hash: [0u8; 32],
signature: [1u8; 64], // Non-zero placeholder
};
assert!(!token.is_valid(1_200_000));
}
#[test]
fn test_permit_token_defer_always_invalid() {
let token = PermitToken {
decision: GateDecision::Defer,
sequence: 0,
timestamp: 1_000_000,
ttl_ns: 500_000,
witness_hash: [0u8; 32],
signature: [1u8; 64], // Non-zero placeholder
};
assert!(!token.is_valid(1_200_000));
}
#[test]
fn test_permit_token_issuance_from_tilezero() {
let thresholds = GateThresholds::default();
let mut tilezero = TileZero::new(thresholds);
let reports: Vec<TileReport> = (1..=3)
.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();
let decision = tilezero.merge_reports(reports);
let token = tilezero.issue_permit(&decision);
assert_eq!(token.decision, GateDecision::Permit);
assert!(token.ttl_ns > 0);
}
}
// ============================================================================
// Receipt Log Tests
// ============================================================================
mod receipt_log_tests {
use super::*;
#[test]
fn test_receipt_log_creation() {
let log = ReceiptLog::new();
assert!(log.is_empty());
assert_eq!(log.len(), 0);
}
#[test]
fn test_receipt_log_append() {
let mut log = ReceiptLog::new();
log.append(GateDecision::Permit, 0, 1000, [0u8; 32]);
assert_eq!(log.len(), 1);
assert!(!log.is_empty());
}
#[test]
fn test_receipt_log_get_by_sequence() {
let mut log = ReceiptLog::new();
log.append(GateDecision::Permit, 0, 1000, [0u8; 32]);
log.append(GateDecision::Defer, 1, 2000, [1u8; 32]);
log.append(GateDecision::Deny, 2, 3000, [2u8; 32]);
let entry = log.get(1);
assert!(entry.is_some());
assert_eq!(entry.unwrap().decision, GateDecision::Defer);
assert_eq!(entry.unwrap().sequence, 1);
}
#[test]
fn test_receipt_log_get_nonexistent() {
let mut log = ReceiptLog::new();
log.append(GateDecision::Permit, 0, 1000, [0u8; 32]);
let entry = log.get(999);
assert!(entry.is_none());
}
#[test]
fn test_receipt_log_chain_integrity() {
let mut log = ReceiptLog::new();
log.append(GateDecision::Permit, 0, 1000, [0u8; 32]);
log.append(GateDecision::Permit, 1, 2000, [1u8; 32]);
log.append(GateDecision::Permit, 2, 3000, [2u8; 32]);
// Each entry's previous_hash should match prior entry's hash
let entry1 = log.get(1).unwrap();
let entry2 = log.get(2).unwrap();
assert_eq!(entry2.previous_hash, entry1.hash);
}
#[test]
fn test_receipt_log_last_hash() {
let mut log = ReceiptLog::new();
let initial_hash = log.last_hash();
assert_eq!(initial_hash, [0u8; 32]);
log.append(GateDecision::Permit, 0, 1000, [0u8; 32]);
let new_hash = log.last_hash();
assert_ne!(new_hash, [0u8; 32]);
}
#[test]
fn test_receipt_log_multiple_decisions() {
let mut log = ReceiptLog::new();
for i in 0..100 {
let decision = match i % 3 {
0 => GateDecision::Permit,
1 => GateDecision::Defer,
_ => GateDecision::Deny,
};
log.append(decision, i, i * 1000, [i as u8; 32]);
}
assert_eq!(log.len(), 100);
for i in 0..100 {
let entry = log.get(i);
assert!(entry.is_some());
assert_eq!(entry.unwrap().sequence, i);
}
}
}
// ============================================================================
// PatchGraph Tests
// ============================================================================
mod patch_graph_tests {
use super::*;
#[test]
fn test_patch_graph_creation() {
let graph = PatchGraph::new();
assert_eq!(graph.num_vertices, 0);
assert_eq!(graph.num_edges, 0);
assert_eq!(graph.num_components, 0);
}
#[test]
fn test_patch_graph_add_edge() {
let mut graph = PatchGraph::new();
let edge_id = graph.add_edge(0, 1, 100);
assert!(edge_id.is_some());
assert_eq!(graph.num_edges, 1);
assert_eq!(graph.num_vertices, 2);
}
#[test]
fn test_patch_graph_add_multiple_edges() {
let mut graph = PatchGraph::new();
graph.add_edge(0, 1, 100);
graph.add_edge(1, 2, 100);
graph.add_edge(2, 0, 100);
assert_eq!(graph.num_edges, 3);
assert_eq!(graph.num_vertices, 3);
}
#[test]
fn test_patch_graph_remove_edge() {
let mut graph = PatchGraph::new();
graph.add_edge(0, 1, 100);
assert!(graph.remove_edge(0, 1));
assert_eq!(graph.num_edges, 0);
}
#[test]
fn test_patch_graph_remove_nonexistent() {
let mut graph = PatchGraph::new();
assert!(!graph.remove_edge(0, 1));
}
#[test]
fn test_patch_graph_find_edge() {
let mut graph = PatchGraph::new();
let edge_id = graph.add_edge(0, 1, 100).unwrap();
assert_eq!(graph.find_edge(0, 1), Some(edge_id));
assert_eq!(graph.find_edge(1, 0), Some(edge_id));
assert_eq!(graph.find_edge(0, 2), None);
}
#[test]
fn test_patch_graph_update_weight() {
let mut graph = PatchGraph::new();
graph.add_edge(0, 1, 100);
assert!(graph.update_weight(0, 1, 200));
// Verify weight updated
let edge_id = graph.find_edge(0, 1).unwrap();
assert_eq!(graph.edges[edge_id as usize].weight, 200);
}
#[test]
fn test_patch_graph_components() {
let mut graph = PatchGraph::new();
// Create two disconnected components
graph.add_edge(0, 1, 100);
graph.add_edge(2, 3, 100);
graph.recompute_components();
assert_eq!(graph.num_components, 2);
}
#[test]
fn test_patch_graph_connected_components() {
let mut graph = PatchGraph::new();
// Create one connected component
graph.add_edge(0, 1, 100);
graph.add_edge(1, 2, 100);
graph.add_edge(2, 3, 100);
graph.recompute_components();
assert_eq!(graph.num_components, 1);
}
#[test]
fn test_patch_graph_estimate_local_cut() {
let mut graph = PatchGraph::new();
graph.add_edge(0, 1, 100);
graph.add_edge(1, 2, 100);
let cut = graph.estimate_local_cut();
assert!(cut > 0.0);
}
#[test]
fn test_patch_graph_boundary_candidates() {
let mut graph = PatchGraph::new();
// Add edges with varying weights
graph.add_edge(0, 1, 10);
graph.add_edge(1, 2, 100);
graph.add_edge(2, 3, 50);
let mut candidates = [0u16; MAX_BOUNDARY_CANDIDATES];
let count = graph.identify_boundary_candidates(&mut candidates);
// Should identify some boundary candidates
assert!(count <= MAX_BOUNDARY_CANDIDATES);
}
#[test]
fn test_patch_graph_clear() {
let mut graph = PatchGraph::new();
graph.add_edge(0, 1, 100);
graph.add_edge(1, 2, 100);
graph.clear();
assert_eq!(graph.num_vertices, 0);
assert_eq!(graph.num_edges, 0);
}
#[test]
fn test_patch_graph_self_loop_rejected() {
let mut graph = PatchGraph::new();
let result = graph.add_edge(0, 0, 100);
assert!(result.is_none());
}
#[test]
fn test_patch_graph_max_vertices() {
let mut graph = PatchGraph::new();
// Attempt to add edge with vertex beyond max
let result = graph.add_edge(MAX_PATCH_VERTICES as u16, 0, 100);
assert!(result.is_none());
}
#[test]
fn test_patch_graph_apply_delta() {
let mut graph = PatchGraph::new();
// Apply edge add delta
let delta = SyndromeDelta::edge_add(0, 1, 100);
graph.apply_delta(&delta);
assert_eq!(graph.num_edges, 1);
// Apply edge remove delta
let delta = SyndromeDelta::edge_remove(0, 1);
graph.apply_delta(&delta);
assert_eq!(graph.num_edges, 0);
}
}
// ============================================================================
// SyndromBuffer Tests
// ============================================================================
mod syndrom_buffer_tests {
use super::*;
#[test]
fn test_syndrome_buffer_creation() {
let buffer = SyndromBuffer::new();
assert_eq!(buffer.count, 0);
assert_eq!(buffer.head, 0);
}
#[test]
fn test_syndrome_buffer_append() {
let mut buffer = SyndromBuffer::new();
let entry = SyndromeEntry {
round: 1,
syndrome: [0; 8],
flags: 0,
};
buffer.append(entry);
assert_eq!(buffer.count, 1);
assert_eq!(buffer.current_round, 1);
}
#[test]
fn test_syndrome_buffer_ring_behavior() {
let mut buffer = SyndromBuffer::new();
// Fill beyond capacity
for i in 0..SYNDROME_BUFFER_DEPTH + 100 {
let entry = SyndromeEntry {
round: i as u32,
syndrome: [i as u8; 8],
flags: 0,
};
buffer.append(entry);
}
// Count should be capped at depth
assert_eq!(buffer.count as usize, SYNDROME_BUFFER_DEPTH);
}
#[test]
fn test_syndrome_buffer_recent() {
let mut buffer = SyndromBuffer::new();
for i in 0..100 {
let entry = SyndromeEntry {
round: i,
syndrome: [i as u8; 8],
flags: 0,
};
buffer.append(entry);
}
let recent: Vec<_> = buffer.recent(10).collect();
assert_eq!(recent.len(), 10);
// Should be most recent 10 entries
assert_eq!(recent[0].round, 90);
assert_eq!(recent[9].round, 99);
}
#[test]
fn test_syndrome_buffer_recent_more_than_available() {
let mut buffer = SyndromBuffer::new();
for i in 0..5 {
let entry = SyndromeEntry {
round: i,
syndrome: [0; 8],
flags: 0,
};
buffer.append(entry);
}
let recent: Vec<_> = buffer.recent(100).collect();
assert_eq!(recent.len(), 5);
}
#[test]
fn test_syndrome_buffer_clear() {
let mut buffer = SyndromBuffer::new();
for i in 0..50 {
let entry = SyndromeEntry {
round: i,
syndrome: [0; 8],
flags: 0,
};
buffer.append(entry);
}
buffer.clear();
assert_eq!(buffer.count, 0);
assert_eq!(buffer.head, 0);
assert_eq!(buffer.current_round, 0);
}
}
// ============================================================================
// EvidenceAccumulator Tests (Tile Module)
// ============================================================================
mod tile_evidence_tests {
use super::*;
#[test]
fn test_evidence_accumulator_initial() {
let acc = EvidenceAccumulator::new();
assert_eq!(acc.log_e_value, 0);
assert_eq!(acc.obs_count, 0);
assert_eq!(acc.e_value(), 1.0);
}
#[test]
fn test_evidence_accumulator_observe() {
let mut acc = EvidenceAccumulator::new();
acc.observe(10000); // Positive log LR
assert!(acc.log_e_value != 0);
assert_eq!(acc.obs_count, 1);
}
#[test]
fn test_evidence_accumulator_significance() {
let mut acc = EvidenceAccumulator::new();
// Accumulate enough evidence for significance
for _ in 0..100 {
acc.observe(100000); // Strong positive evidence
}
assert!(acc.is_significant());
}
#[test]
fn test_evidence_accumulator_reset() {
let mut acc = EvidenceAccumulator::new();
for _ in 0..50 {
acc.observe(10000);
}
acc.reset();
assert_eq!(acc.log_e_value, 0);
assert_eq!(acc.obs_count, 0);
assert_eq!(acc.e_value(), 1.0);
}
}
// ============================================================================
// LocalCutState Tests
// ============================================================================
mod local_cut_state_tests {
use super::*;
#[test]
fn test_local_cut_state_creation() {
let state = LocalCutState::new();
assert_eq!(state.cut_value, 0.0);
assert_eq!(state.prev_cut_value, 0.0);
assert_eq!(state.num_candidates, 0);
}
#[test]
fn test_local_cut_state_update_from_graph() {
let mut graph = PatchGraph::new();
graph.add_edge(0, 1, 100);
graph.add_edge(1, 2, 100);
graph.recompute_components();
let mut state = LocalCutState::new();
state.update_from_graph(&graph);
assert!(state.cut_value > 0.0);
assert!(state.generation > 0);
}
#[test]
fn test_local_cut_state_candidates() {
let mut graph = PatchGraph::new();
graph.add_edge(0, 1, 10);
graph.add_edge(1, 2, 100);
graph.add_edge(2, 3, 50);
let mut state = LocalCutState::new();
state.update_from_graph(&graph);
let candidates = state.candidates();
assert!(candidates.len() <= MAX_BOUNDARY_CANDIDATES);
}
}
// ============================================================================
// 256-Tile Scaling Tests
// ============================================================================
mod scaling_tests {
use super::*;
#[test]
fn test_256_tile_fabric_creation() {
let workers: Vec<WorkerTile> = (1..=255).map(WorkerTile::new).collect();
assert_eq!(workers.len(), NUM_WORKERS);
// Verify all tile IDs are unique
let mut seen = [false; 256];
for worker in &workers {
assert!(!seen[worker.tile_id as usize]);
seen[worker.tile_id as usize] = true;
}
}
#[test]
fn test_all_tiles_produce_valid_reports() {
let workers: Vec<WorkerTile> = (1..=10).map(WorkerTile::new).collect();
for mut worker in workers {
let delta = SyndromeDelta::new(0, 1, 50);
let report = worker.tick(&delta);
assert!(report.status & TileReport::STATUS_VALID != 0);
}
}
#[test]
fn test_tilezero_handles_255_reports() {
let thresholds = GateThresholds::default();
let mut tilezero = TileZero::new(thresholds);
let reports: Vec<TileReport> = (1..=255)
.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();
let decision = tilezero.merge_reports(reports);
assert_eq!(decision, GateDecision::Permit);
}
#[test]
fn test_memory_budget_per_tile() {
let tile_size = WorkerTile::memory_size();
// Each tile should fit within 64KB budget (with some margin)
// The spec says ~64KB, so we allow up to 128KB
assert!(
tile_size <= 131072,
"Worker tile exceeds memory budget: {} bytes",
tile_size
);
}
}
// ============================================================================
// Proptest Property-Based Tests
// ============================================================================
#[cfg(test)]
mod proptest_tiles {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn prop_worker_tick_always_increments(
tile_id in 1u8..255,
num_ticks in 1usize..100
) {
let mut tile = WorkerTile::new(tile_id);
for _ in 0..num_ticks {
let delta = SyndromeDelta::new(0, 1, 50);
tile.tick(&delta);
}
prop_assert_eq!(tile.tick, num_ticks as u32);
}
#[test]
fn prop_report_matches_tile_id(tile_id in 1u8..255) {
let mut tile = WorkerTile::new(tile_id);
let delta = SyndromeDelta::new(0, 1, 50);
let report = tile.tick(&delta);
prop_assert_eq!(report.tile_id, tile_id);
}
#[test]
fn prop_receipt_log_sequence_ordered(num_decisions in 1usize..50) {
let thresholds = GateThresholds::default();
let mut tilezero = TileZero::new(thresholds);
for _ in 0..num_decisions {
let reports: Vec<TileReport> = (1..=3)
.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);
}
// Verify all sequences exist
for i in 0..num_decisions {
let entry = tilezero.receipt_log.get(i as u64);
prop_assert!(entry.is_some());
prop_assert_eq!(entry.unwrap().sequence, i as u64);
}
}
}
}