use grovewright_core::codec::DecodeError; use grovewright_core::command::Command; use grovewright_core::map::Pos; use grovewright_core::mapgen::outcrop_origin; use grovewright_core::player::PlayerId; use grovewright_core::snapshot::{SnapshotError, SNAPSHOT_VERSION}; use grovewright_core::world::World; const ADA: PlayerId = PlayerId(0); fn rock(world: &World) -> Pos { outcrop_origin(world.map()) } /// A world at tick 12 with one player who has hit a rock once. fn played_world() -> World { let mut world = World::new(7); world.step(&[Command::Join { key: [3; 16], name: "Ada".to_owned(), }]); let pos = rock(&world); world.step(&[Command::Dig { player: ADA, pos }]); for _ in 0..10 { world.step(&[]); } world } #[test] fn a_snapshot_restores_an_identical_world() { let world = played_world(); let restored = World::from_snapshot(&world.to_snapshot()).unwrap(); assert_eq!(restored, world); assert_eq!(restored.state_hash(), world.state_hash()); } #[test] fn the_state_hash_covers_every_part_of_the_state() { let base = played_world(); let pos = rock(&base); let mut waited = base.clone(); waited.step(&[]); assert_ne!(waited.state_hash(), base.state_hash(), "tick"); let mut dug = base.clone(); dug.step(&[Command::Dig { player: ADA, pos }]); assert_ne!(dug.state_hash(), waited.state_hash(), "map and cooldown"); let mut left = base.clone(); left.step(&[Command::Leave { player: ADA }]); assert_ne!(left.state_hash(), waited.state_hash(), "players"); assert_ne!( World::new(1).state_hash(), World::new(2).state_hash(), "seed" ); } #[test] fn equal_worlds_have_equal_hashes() { assert_eq!(played_world().state_hash(), played_world().state_hash()); } #[test] fn a_restored_world_continues_exactly_like_the_original() { let mut original = played_world(); let mut restored = World::from_snapshot(&original.to_snapshot()).unwrap(); let pos = rock(&original); for tick in 0..40u32 { let commands = if tick.is_multiple_of(6) { vec![Command::Dig { player: ADA, pos }] } else { vec![] }; assert_eq!(original.step(&commands), restored.step(&commands)); } assert_eq!(original.state_hash(), restored.state_hash()); assert_eq!(original, restored); } #[test] fn bytes_that_are_not_a_snapshot_are_rejected() { assert_eq!(World::from_snapshot(b""), Err(SnapshotError::NotASnapshot)); assert_eq!( World::from_snapshot(b"GR"), Err(SnapshotError::NotASnapshot) ); assert_eq!( World::from_snapshot(b"nope, not a save"), Err(SnapshotError::NotASnapshot) ); } #[test] fn snapshots_of_another_version_are_rejected() { let mut bytes = played_world().to_snapshot(); bytes[4..8].copy_from_slice(&(SNAPSHOT_VERSION + 1).to_le_bytes()); assert_eq!( World::from_snapshot(&bytes), Err(SnapshotError::UnsupportedVersion(SNAPSHOT_VERSION + 1)) ); } #[test] fn cut_off_and_padded_snapshots_are_rejected() { let bytes = played_world().to_snapshot(); assert_eq!( World::from_snapshot(&bytes[..bytes.len() - 1]), Err(SnapshotError::Corrupt(DecodeError::UnexpectedEnd)) ); let mut padded = bytes.clone(); padded.push(0); assert_eq!( World::from_snapshot(&padded), Err(SnapshotError::Corrupt(DecodeError::TrailingBytes)) ); }