#![cfg(feature = "simulation_tests")]
use freenet::dev_tool::SimNetwork;
use std::collections::HashMap;
use std::time::Duration;
async fn let_network_run(sim: &mut SimNetwork, duration: Duration) {
let step = Duration::from_millis(100);
let mut elapsed = Duration::ZERO;
while elapsed < duration {
sim.advance_time(step);
tokio::task::yield_now().await;
tokio::time::sleep(Duration::from_millis(10)).await;
elapsed += step;
}
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_smoke_event_types_consistency() {
use freenet::config::{GlobalRng, GlobalSimulationTime};
const SEED: u64 = 0xFA01_7777_1234;
async fn run_simulation(name: &str, seed: u64) -> HashMap<String, usize> {
GlobalRng::set_seed(seed);
const BASE_EPOCH_MS: u64 = 1577836800000; const RANGE_MS: u64 = 5 * 365 * 24 * 60 * 60 * 1000; GlobalSimulationTime::set_time_ms(BASE_EPOCH_MS + (seed % RANGE_MS));
let mut sim = SimNetwork::new(name, 1, 3, 7, 3, 10, 2, seed).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(seed, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(3)).await;
sim.get_event_counts().await
}
let events1 = run_simulation("fault-run1", SEED).await;
let events2 = run_simulation("fault-run2", SEED).await;
let total_events: usize = events1.values().sum();
assert!(total_events > 0, "Should capture events during simulation");
let types1: std::collections::HashSet<&String> = events1.keys().collect();
let types2: std::collections::HashSet<&String> = events2.keys().collect();
assert_eq!(
types1, types2,
"Event types should be consistent.\nRun 1: {:?}\nRun 2: {:?}",
events1, events2
);
tracing::info!(
"Event types consistency test passed - {} events captured",
total_events
);
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_event_summary_ordering() {
const SEED: u64 = 0xC0DE_CAFE_BABE;
let mut sim = SimNetwork::new("event-order", 1, 3, 7, 3, 10, 2, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(3)).await;
let summary = sim.get_deterministic_event_summary().await;
let mut sorted = summary.clone();
sorted.sort();
assert_eq!(summary, sorted, "Event summary should be sorted");
let connect_events = summary
.iter()
.filter(|e| e.event_kind_name == "Connect")
.count();
assert!(
connect_events > 0,
"Should have Connect events, got event kinds: {:?}",
summary
.iter()
.map(|e| &e.event_kind_name)
.collect::<Vec<_>>()
);
tracing::info!(
"Event ordering test passed - {} events, {} Connect events",
summary.len(),
connect_events
);
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_smoke_small_network() {
const SEED: u64 = 0x5A11_1111;
let mut sim = SimNetwork::new("small-network", 1, 2, 7, 3, 5, 1, SEED).await;
sim.with_start_backoff(Duration::from_millis(30));
let handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
assert_eq!(handles.len(), 3, "Expected 1 gateway + 2 nodes = 3 handles");
let_network_run(&mut sim, Duration::from_secs(2)).await;
let event_counts = sim.get_event_counts().await;
let total_events: usize = event_counts.values().sum();
assert!(
total_events > 0,
"Should have captured events during network startup"
);
match sim
.check_partial_connectivity(Duration::from_secs(10), 0.5)
.await
{
Ok(()) => {
tracing::info!("Small network achieved connectivity");
}
Err(e) => {
tracing::warn!("Connectivity check: {}", e);
}
}
tracing::info!(
"Small network test completed - captured {} events",
total_events
);
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_peer_label_assignment() {
const SEED: u64 = 0x1ABE_1234;
let mut sim = SimNetwork::new("label-test", 2, 3, 7, 3, 10, 2, SEED).await;
let peers = sim.build_peers();
let gateway_count = peers.iter().filter(|(l, _)| !l.is_node()).count();
let node_count = peers.iter().filter(|(l, _)| l.is_node()).count();
assert_eq!(gateway_count, 2, "Expected 2 gateways");
assert_eq!(node_count, 3, "Expected 3 nodes");
assert_eq!(peers.len(), 5, "Expected 5 total peers");
for (label, _config) in &peers {
let label_str = label.to_string();
assert!(
label_str.contains("-gateway-") || label_str.contains("-node-"),
"Unexpected label format: {}",
label_str
);
}
tracing::info!("Peer label assignment test passed");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_event_state_hash_capture() {
const SEED: u64 = 0xC0DE_1234;
let mut sim = SimNetwork::new(
"consistency-test",
1, 3, 7, 3, 10, 2, SEED,
)
.await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 5, 10) .await;
let_network_run(&mut sim, Duration::from_secs(5)).await;
let summary = sim.get_deterministic_event_summary().await;
let put_events: Vec<_> = summary
.iter()
.filter(|e| e.event_kind_name == "Put")
.collect();
let state_hashes: Vec<&str> = put_events
.iter()
.filter_map(|e| e.state_hash.as_deref())
.collect();
tracing::info!(
"State hash capture test: {} put events, {} state hashes found",
put_events.len(),
state_hashes.len()
);
for hash in &state_hashes {
assert_eq!(
hash.len(),
8,
"State hash should be 8 hex characters, got: {}",
hash
);
assert!(
hash.chars().all(|c| c.is_ascii_hexdigit()),
"State hash should be hex: {}",
hash
);
}
let connect_events = summary
.iter()
.filter(|e| e.event_kind_name == "Connect")
.count();
assert!(
connect_events > 0,
"Should capture Connect events during network startup"
);
tracing::info!(
"Event state hash capture test passed - {} Connect events",
connect_events
);
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_eventual_consistency_state_hashes() {
const SEED: u64 = 0xC0DE_5678;
let mut sim = SimNetwork::new(
"eventual-consistency",
1, 4, 7, 3, 10, 2, SEED,
)
.await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 3, 15) .await;
let_network_run(&mut sim, Duration::from_secs(6)).await;
let summary = sim.get_deterministic_event_summary().await;
let mut contract_state_by_peer: HashMap<String, Vec<(std::net::SocketAddr, String)>> =
HashMap::new();
for event in &summary {
if let (Some(contract_key), Some(state_hash)) = (&event.contract_key, &event.state_hash) {
contract_state_by_peer
.entry(contract_key.clone())
.or_default()
.push((event.peer_addr, state_hash.clone()));
}
}
tracing::info!(
"Found {} contracts with state hashes across peers",
contract_state_by_peer.len()
);
let mut consistent_contracts = 0;
let mut total_contracts_with_multiple_peers = 0;
for (contract_key, peer_states) in &contract_state_by_peer {
if peer_states.len() < 2 {
continue; }
total_contracts_with_multiple_peers += 1;
let mut peer_final_states: HashMap<std::net::SocketAddr, String> = HashMap::new();
for (peer_addr, state_hash) in peer_states {
peer_final_states.insert(*peer_addr, state_hash.clone());
}
let unique_states: std::collections::HashSet<&String> =
peer_final_states.values().collect();
if unique_states.len() == 1 {
consistent_contracts += 1;
tracing::debug!(
"Contract {} is consistent across {} peers",
contract_key,
peer_final_states.len()
);
} else {
tracing::warn!(
"Contract {} has {} different states across {} peers: {:?}",
contract_key,
unique_states.len(),
peer_final_states.len(),
peer_final_states
);
}
}
tracing::info!(
"Eventual consistency: {}/{} contracts consistent",
consistent_contracts,
total_contracts_with_multiple_peers
);
let total_events: usize = summary.len();
assert!(total_events > 0, "Should have captured events during test");
if total_contracts_with_multiple_peers > 0 {
let convergence_rate =
consistent_contracts as f64 / total_contracts_with_multiple_peers as f64;
tracing::info!(
"Convergence rate: {:.1}% ({}/{})",
convergence_rate * 100.0,
consistent_contracts,
total_contracts_with_multiple_peers
);
assert!(
convergence_rate >= 0.5,
"Expected at least 50% of contracts to converge, got {:.1}%",
convergence_rate * 100.0
);
}
let report = sim.verify_state().await;
tracing::info!(
"=== ANOMALY DETECTION (eventual-consistency): {} events, {} state events, {} contracts, {} anomalies ===",
report.total_events,
report.state_events,
report.contracts_analyzed,
report.anomalies.len()
);
for (i, anomaly) in report.anomalies.iter().enumerate() {
tracing::warn!(" anomaly[{}] = {:?}", i, anomaly);
}
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_fault_injection_bridge() {
use freenet::simulation::FaultConfig;
const SEED: u64 = 0xFA17_B21D;
let mut sim_normal = SimNetwork::new("fault-bridge-normal", 1, 3, 7, 3, 10, 2, SEED).await;
sim_normal.with_start_backoff(Duration::from_millis(50));
let _handles = sim_normal
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 3)
.await;
let_network_run(&mut sim_normal, Duration::from_secs(3)).await;
let normal_events = sim_normal.get_event_counts().await;
let normal_total: usize = normal_events.values().sum();
let normal_report = sim_normal.verify_state().await;
tracing::info!(
"=== ANOMALY DETECTION (normal run): {} events, {} state events, {} anomalies ===",
normal_report.total_events,
normal_report.state_events,
normal_report.anomalies.len()
);
for (i, anomaly) in normal_report.anomalies.iter().enumerate() {
tracing::warn!(" normal anomaly[{}] = {:?}", i, anomaly);
}
sim_normal.clear_fault_injection();
tracing::info!("Normal run completed: {} events", normal_total);
let mut sim_lossy = SimNetwork::new("fault-bridge-lossy", 1, 3, 7, 3, 10, 2, SEED).await;
sim_lossy.with_start_backoff(Duration::from_millis(50));
let fault_config = FaultConfig::builder().message_loss_rate(0.5).build();
sim_lossy.with_fault_injection(fault_config);
let _handles = sim_lossy
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 3)
.await;
let_network_run(&mut sim_lossy, Duration::from_secs(3)).await;
let lossy_events = sim_lossy.get_event_counts().await;
let lossy_total: usize = lossy_events.values().sum();
let lossy_report = sim_lossy.verify_state().await;
tracing::info!(
"=== ANOMALY DETECTION (50% loss run): {} events, {} state events, {} anomalies ===",
lossy_report.total_events,
lossy_report.state_events,
lossy_report.anomalies.len()
);
for (i, anomaly) in lossy_report.anomalies.iter().enumerate() {
tracing::warn!(" lossy anomaly[{}] = {:?}", i, anomaly);
}
tracing::info!(
"=== ANOMALY COMPARISON: normal={} anomalies vs lossy={} anomalies ===",
normal_report.anomalies.len(),
lossy_report.anomalies.len()
);
sim_lossy.clear_fault_injection();
tracing::info!("Lossy run (50% loss) completed: {} events", lossy_total);
assert!(normal_total > 0, "Normal run should capture events");
assert!(
lossy_total > 0,
"Lossy run should still capture some events"
);
tracing::info!("Fault injection bridge test passed");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_partition_injection_bridge() {
use freenet::simulation::{FaultConfig, Partition};
use std::collections::HashSet;
const SEED: u64 = 0xDA27_1710;
let mut sim = SimNetwork::new("partition-test", 1, 2, 7, 3, 10, 2, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 3)
.await;
let_network_run(&mut sim, Duration::from_secs(2)).await;
let pre_partition = sim.get_event_counts().await;
let pre_total: usize = pre_partition.values().sum();
tracing::info!("Pre-partition: {} events", pre_total);
let all_addrs = sim.all_node_addresses();
assert!(
all_addrs.len() >= 2,
"Need at least 2 nodes for partition test"
);
let addrs: Vec<_> = all_addrs.values().copied().collect();
let mid = addrs.len() / 2;
let side_a: HashSet<_> = addrs[..mid].iter().copied().collect();
let side_b: HashSet<_> = addrs[mid..].iter().copied().collect();
let partition = Partition::new(side_a, side_b).permanent(0);
let fault_config = FaultConfig::builder().partition(partition).build();
sim.with_fault_injection(fault_config);
let_network_run(&mut sim, Duration::from_secs(2)).await;
sim.clear_fault_injection();
tracing::info!("Partition injection bridge test passed");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_latency_injection() {
use freenet::simulation::FaultConfig;
const SEED: u64 = 0x1A7E_1234;
let mut sim_fast = SimNetwork::new("latency-none", 1, 2, 7, 3, 10, 2, SEED).await;
sim_fast.with_start_backoff(Duration::from_millis(30));
let _handles = sim_fast
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 2)
.await;
let_network_run(&mut sim_fast, Duration::from_secs(2)).await;
let fast_events = sim_fast.get_event_counts().await;
let fast_total: usize = fast_events.values().sum();
let mut sim_slow = SimNetwork::new("latency-injected", 1, 2, 7, 3, 10, 2, SEED).await;
sim_slow.with_start_backoff(Duration::from_millis(30));
let fault_config = FaultConfig::builder()
.latency_range(Duration::from_millis(100)..Duration::from_millis(200))
.build();
sim_slow.with_fault_injection(fault_config);
let _handles = sim_slow
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 2)
.await;
let_network_run(&mut sim_slow, Duration::from_secs(2)).await;
let slow_events = sim_slow.get_event_counts().await;
let slow_total: usize = slow_events.values().sum();
sim_slow.clear_fault_injection();
tracing::info!(
"Latency test: fast={} events, slow={} events",
fast_total,
slow_total
);
assert!(fast_total > 0, "Fast run should capture events");
assert!(slow_total > 0, "Slow run should capture some events");
tracing::info!("Latency injection test passed");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_node_crash_recovery() {
const SEED: u64 = 0xC2A5_0000_000E;
let mut sim = SimNetwork::new("crash-recovery-test", 1, 3, 7, 3, 10, 2, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(2)).await;
let all_addrs = sim.all_node_addresses();
assert!(!all_addrs.is_empty(), "Should have tracked node addresses");
let node_to_crash = all_addrs
.keys()
.find(|label| label.is_node())
.cloned()
.expect("Should have at least one regular node");
assert!(
!sim.is_node_crashed(&node_to_crash),
"Node should not be crashed initially"
);
let crashed = sim.crash_node(&node_to_crash);
assert!(crashed, "crash_node should return true for running node");
assert!(
sim.is_node_crashed(&node_to_crash),
"Node should be marked as crashed after crash_node()"
);
let_network_run(&mut sim, Duration::from_millis(500)).await;
let recovered = sim.recover_node(&node_to_crash);
assert!(recovered, "recover_node should return true");
assert!(
!sim.is_node_crashed(&node_to_crash),
"Node should not be crashed after recovery"
);
tracing::info!("Node crash/recovery test completed successfully");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_virtual_time_always_enabled() {
use freenet::dev_tool::TimeSource;
const SEED: u64 = 0x1111_7111;
let mut sim = SimNetwork::new("virtual-time-test", 1, 2, 7, 3, 10, 2, SEED).await;
let vt = sim.virtual_time();
assert_eq!(vt.now_nanos(), 0, "VirtualTime should start at 0");
vt.advance(Duration::from_millis(100));
assert_eq!(vt.now_nanos(), 100_000_000);
let stats = sim.get_network_stats();
assert!(stats.is_some());
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(1)).await;
let _ = sim.virtual_time().now_nanos();
tracing::info!("VirtualTime always-enabled test completed");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_node_restart() {
const SEED: u64 = 0x2E57_A2F0;
let mut sim = SimNetwork::new("restart-test", 1, 3, 7, 3, 10, 2, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(2)).await;
let all_addrs = sim.all_node_addresses();
let node_to_restart = all_addrs
.keys()
.find(|label| label.is_node())
.cloned()
.expect("Should have at least one regular node");
assert!(sim.can_restart(&node_to_restart));
let addr_before = sim.node_address(&node_to_restart);
assert!(addr_before.is_some());
let crashed = sim.crash_node(&node_to_restart);
assert!(crashed);
let_network_run(&mut sim, Duration::from_millis(200)).await;
let restart_seed = SEED.wrapping_add(0x1000);
let handle = sim
.restart_node::<rand::rngs::SmallRng>(&node_to_restart, restart_seed, 1, 1)
.await;
assert!(handle.is_some());
assert!(!sim.is_node_crashed(&node_to_restart));
let addr_after = sim.node_address(&node_to_restart);
assert_eq!(addr_before, addr_after);
let_network_run(&mut sim, Duration::from_secs(2)).await;
tracing::info!("Node restart test completed successfully");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_crash_restart_edge_cases() {
use freenet::dev_tool::NodeLabel;
const SEED: u64 = 0xED6E_CA5E;
let mut sim = SimNetwork::new("crash-edge-cases", 1, 2, 7, 3, 10, 2, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(1)).await;
let fake_label: NodeLabel = "node-999".into();
assert!(!sim.crash_node(&fake_label));
assert!(!sim.recover_node(&fake_label));
assert!(!sim.is_node_crashed(&fake_label));
assert!(!sim.can_restart(&fake_label));
assert!(sim.node_address(&fake_label).is_none());
tracing::info!("Edge case tests completed successfully");
}
#[test_log::test(tokio::test)]
#[should_panic(expected = "assertion failed")]
async fn test_zero_nodes_panics() {
let _sim = SimNetwork::new("zero-nodes-test", 1, 0, 7, 3, 10, 2, 0xDEAD).await;
}
#[test_log::test(tokio::test)]
#[should_panic(expected = "should have at least one gateway")]
async fn test_zero_gateways_panics() {
let _sim = SimNetwork::new("zero-gateways-test", 0, 2, 7, 3, 10, 2, 0xDEAD).await;
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_minimal_network() {
const SEED: u64 = 0x0101_0401;
let mut sim = SimNetwork::new("minimal-test", 1, 1, 7, 3, 10, 2, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
assert_eq!(handles.len(), 2, "Should have exactly 2 nodes");
let_network_run(&mut sim, Duration::from_secs(1)).await;
let all_addrs = sim.all_node_addresses();
assert_eq!(all_addrs.len(), 2, "Should track 2 node addresses");
tracing::info!("Minimal network test passed");
}
fn make_contract_id(seed: u8) -> freenet_stdlib::prelude::ContractInstanceId {
freenet_stdlib::prelude::ContractInstanceId::new([seed; 32])
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_topology_infrastructure() {
use freenet::config::{GlobalRng, GlobalSimulationTime};
const SEED: u64 = 0x1234_5678;
GlobalRng::set_seed(SEED);
const BASE_EPOCH_MS: u64 = 1577836800000;
const RANGE_MS: u64 = 5 * 365 * 24 * 60 * 60 * 1000;
GlobalSimulationTime::set_time_ms(BASE_EPOCH_MS + (SEED % RANGE_MS));
freenet::dev_tool::RequestId::reset_counter();
freenet::dev_tool::ClientId::reset_counter();
freenet::dev_tool::reset_event_id_counter();
freenet::dev_tool::reset_channel_id_counter();
freenet::dev_tool::StreamId::reset_counter();
freenet::dev_tool::reset_nonce_counter();
freenet::dev_tool::reset_global_node_index();
let mut sim = SimNetwork::new(
"topology-infra-test",
1, 3, 7, 3, 10, 2, SEED,
)
.await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(5)).await;
tokio::time::sleep(Duration::from_secs(3)).await;
let snapshots = sim.get_topology_snapshots();
assert!(
snapshots.len() >= 4,
"Expected at least 4 topology snapshots (1 gateway + 3 nodes), got {}",
snapshots.len()
);
for snap in &snapshots {
assert!(
snap.location >= 0.0 && snap.location <= 1.0,
"Invalid location {} for peer {}",
snap.location,
snap.peer_addr
);
}
let contract_id = make_contract_id(1);
let result = sim.validate_subscription_topology(&contract_id, 0.5);
assert!(
result.is_healthy(),
"Empty topology should be healthy, got {} issues",
result.issue_count
);
tracing::info!("Topology infrastructure test passed");
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_stale_reservation_allows_gateway_retry() {
let mut sim = SimNetwork::new("stale-retry", 1, 2, 7, 3, 10, 2, 0x2888_0001).await;
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(0x2888_0001, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(3)).await;
let gw = freenet::dev_tool::NodeLabel::gateway("stale-retry", 0);
let phantom_addr: std::net::SocketAddr = "127.99.99.1:9999".parse().unwrap();
let phantom_loc = freenet::dev_tool::Location::new(0.55);
assert!(sim.inject_stale_reservation(&gw, phantom_addr, phantom_loc, Duration::ZERO));
assert_eq!(sim.has_connection_or_pending(&gw, phantom_addr), Some(true));
assert!(
sim.inject_stale_reservation(&gw, phantom_addr, phantom_loc, Duration::from_secs(120),)
);
assert_eq!(
sim.has_connection_or_pending(&gw, phantom_addr),
Some(false)
);
assert_eq!(
sim.should_accept(&gw, phantom_loc, phantom_addr),
Some(true)
);
assert_eq!(sim.has_connection_or_pending(&gw, phantom_addr), Some(true));
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_stale_reservation_cleanup_frees_capacity() {
let mut sim = SimNetwork::new("cleanup-cap", 1, 1, 7, 3, 5, 1, 0x2888_0002).await;
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(0x2888_0002, 1, 1)
.await;
let_network_run(&mut sim, Duration::from_secs(3)).await;
let gw = freenet::dev_tool::NodeLabel::gateway("cleanup-cap", 0);
let established = sim.connection_count(&gw).unwrap_or(0);
let stale_count = 5usize.saturating_sub(established);
for i in 0..stale_count {
let addr: std::net::SocketAddr =
format!("127.88.88.{}:{}", i + 1, 7000 + i).parse().unwrap();
let loc = freenet::dev_tool::Location::new(0.1 + (i as f64) * 0.1);
assert!(sim.inject_stale_reservation(&gw, addr, loc, Duration::from_secs(120)));
}
let reserved_before = sim.reserved_connections_count(&gw).unwrap();
assert!(reserved_before >= stale_count);
let new_addr: std::net::SocketAddr = "127.77.77.1:6000".parse().unwrap();
let new_loc = freenet::dev_tool::Location::new(0.75);
assert_eq!(sim.should_accept(&gw, new_loc, new_addr), Some(true));
let removed = sim.cleanup_stale_reservations(&gw).unwrap();
assert!(removed >= stale_count);
let reserved_after = sim.reserved_connections_count(&gw).unwrap();
assert!(reserved_after < reserved_before);
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_connection_growth_past_initial_peers() {
use freenet::dev_tool::NodeLabel;
const SEED: u64 = 0x3303_C0FF_EE01;
const NETWORK_NAME: &str = "conn-growth-3303";
const MIN_CONNECTIONS: usize = 3;
let mut sim = SimNetwork::new(NETWORK_NAME, 1, 5, 7, 3, 6, MIN_CONNECTIONS, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 3, 20)
.await;
let_network_run(&mut sim, Duration::from_secs(60)).await;
for i in 1..=5 {
let label = NodeLabel::node(NETWORK_NAME, i);
let count = sim.connection_count(&label).unwrap_or(0);
assert!(
count >= 2,
"Node {} has {} connections, expected >= 2 — basic ring connectivity is broken \
(each node should connect to gateway + at least one ring peer)",
i,
count,
);
}
}
#[test_log::test(tokio::test(flavor = "current_thread"))]
async fn test_fully_saturated_small_network() {
use freenet::dev_tool::NodeLabel;
const SEED: u64 = 0x3303_C0FF_EE02;
const NETWORK_NAME: &str = "saturation-3303";
let mut sim = SimNetwork::new(NETWORK_NAME, 1, 2, 7, 3, 4, 2, SEED).await;
sim.with_start_backoff(Duration::from_millis(50));
let _handles = sim
.start_with_rand_gen::<rand::rngs::SmallRng>(SEED, 2, 10)
.await;
let_network_run(&mut sim, Duration::from_secs(60)).await;
let gw = NodeLabel::gateway(NETWORK_NAME, 0);
let gw_count = sim.connection_count(&gw).unwrap_or(0);
assert!(gw_count >= 1, "Gateway should have at least 1 connection");
for i in 1..=2 {
let label = NodeLabel::node(NETWORK_NAME, i);
let count = sim.connection_count(&label).unwrap_or(0);
assert!(
count >= 1,
"Node {} should be connected (got 0) — fix must not break fully-saturated networks",
i
);
}
}