test(server): cover async reconcile convergence and eviction race
This commit is contained in:
parent
9001cc6f98
commit
19fdec44d1
|
|
@ -170,8 +170,77 @@ pub fn cylinder_chunks<S: std::hash::BuildHasher>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ChunkPos, cylinder_chunks};
|
||||
use super::{ChunkPos, ServerWorld, StreamStats, cylinder_chunks};
|
||||
use shared::generator::{VoxelGenerator, WorldGenConfig};
|
||||
use shared::world::BlockId;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Builds a server world backed by a real worker pool for streaming tests.
|
||||
fn test_world() -> ServerWorld {
|
||||
let config = WorldGenConfig {
|
||||
base_height: 8,
|
||||
noise_scale: 0.05,
|
||||
surface_block: BlockId(1),
|
||||
subsurface_block: BlockId(2),
|
||||
stone_block: BlockId(3),
|
||||
};
|
||||
ServerWorld::new(VoxelGenerator::new(config, 42))
|
||||
}
|
||||
|
||||
/// Repeatedly reconciles against `desired` until the worker pool reports no outstanding work, returning the final pass's stats. Fails the test if the pool does not drain within a fixed timeout.
|
||||
fn drain_to_idle(world: &mut ServerWorld, desired: &HashSet<ChunkPos>) -> StreamStats {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
loop {
|
||||
let stats = world.reconcile(desired);
|
||||
if stats.in_flight == 0 {
|
||||
return stats;
|
||||
}
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"worker pool did not drain in time"
|
||||
);
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reconcile_converges_over_multiple_passes() {
|
||||
let mut world = test_world();
|
||||
let mut desired = HashSet::new();
|
||||
cylinder_chunks(ChunkPos::new(0, 0, 0), 2, &mut desired);
|
||||
|
||||
// The first pass only dispatches work; because generation is off-thread, nothing is resident yet and every position is in flight.
|
||||
let first = world.reconcile(&desired);
|
||||
assert_eq!(first.loaded, 0);
|
||||
assert_eq!(first.resident, 0);
|
||||
assert!(first.in_flight > 0);
|
||||
|
||||
// Later passes drain finished chunks until the pool is idle, at which point every desired position must be resident.
|
||||
let final_stats = drain_to_idle(&mut world, &desired);
|
||||
assert_eq!(final_stats.in_flight, 0);
|
||||
assert_eq!(final_stats.resident, desired.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evicted_chunk_is_not_repopulated_on_arrival() {
|
||||
let mut world = test_world();
|
||||
let target = ChunkPos::new(0, 0, 0);
|
||||
let mut desired = HashSet::new();
|
||||
desired.insert(target);
|
||||
|
||||
// Dispatch the chunk, then immediately stop wanting it.
|
||||
world.reconcile(&desired);
|
||||
|
||||
// Every subsequent pass reconciles against an empty desired set, so whenever the worker returns the chunk the drain guard discards it rather than resurrecting an unwanted chunk.
|
||||
let empty = HashSet::new();
|
||||
let final_stats = drain_to_idle(&mut world, &empty);
|
||||
assert_eq!(final_stats.in_flight, 0);
|
||||
assert_eq!(
|
||||
final_stats.resident, 0,
|
||||
"a chunk that is no longer wanted must not become resident when it arrives"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cylinder_is_symmetric_and_bounded() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue