#[path = "../../../os-turkfs-journal/src/replay.rs"] mod replay; use replay::{Block, Device, Error, Outcome, Recovery, HOMES}; #[derive(Clone)] struct Disk { live: [Block;3], durable: [Block;3], operations: usize, cut_after: Option } impl Disk { fn completed(&mut self) -> Result<(),()> { self.operations += 1; if self.cut_after == Some(self.operations) { Err(()) } else { Ok(()) } } fn restart(&self, keep_unflushed: bool, cut_after: Option) -> Self { let live=if keep_unflushed {self.live} else {self.durable}; Self {live,durable:live,operations:0,cut_after} } } impl Device for Disk { fn write(&mut self,lba:u64,bytes:&Block)->Result<(),()> { self.live[HOMES.iter().position(|v|*v==lba).unwrap()]=*bytes; self.completed() } fn flush(&mut self)->Result<(),()> { self.durable=self.live;self.completed() } } fn setup()->(Block,Block,Disk) { let mut log=[0;512];log[..128].copy_from_slice(include_bytes!("fixtures/turkfs-journal-v1.bin")); let mut commit=[0;512];commit[..8].copy_from_slice(b"TKCOMMIT"); commit[8..16].copy_from_slice(&7u64.to_le_bytes());commit[16..20].copy_from_slice(&log[28..32]); let live=[[0x44;512],[0x66;512],[0x77;512]]; (log,commit,Disk {live,durable:live,operations:0,cut_after:None}) } #[test] fn os_turkfs_recovery_second_cut_positive() { let mut double_cuts=0; for first_cut in 1..=4 { let (log,commit,mut first)=setup();first.cut_after=Some(first_cut); let mut initial=Recovery::new(&log,&commit).unwrap();let homes=first.live; assert_eq!(initial.replay(&mut first,&homes),Err(Error::Io)); assert_eq!(first.operations,first_cut); for keep_first in [false,true] { let after_first=first.restart(keep_first,None); let remaining=after_first.live.iter().zip(initial.expected()).filter(|(a,b)|a!=b).count(); // A flushed complete first replay has no second write boundary. if remaining==0 { let mut disk=after_first; let mut next=Recovery::new(&log,&commit).unwrap();let homes=disk.live; assert_eq!(next.replay(&mut disk,&homes),Ok(Outcome::AlreadyApplied)); assert_eq!(disk.operations,0); continue; } for second_cut in 1..=remaining+1 { let mut second=after_first.restart(true,Some(second_cut)); let mut retry=Recovery::new(&log,&commit).unwrap();let homes=second.live; assert_eq!(retry.replay(&mut second,&homes),Err(Error::Io)); assert_eq!(second.operations,second_cut); for keep_second in [false,true] { let mut third=second.restart(keep_second,None); let mut fresh=Recovery::new(&log,&commit).unwrap();let homes=third.live; assert!(fresh.replay(&mut third,&homes).is_ok()); assert_eq!(&third.durable,fresh.expected()); let mut fourth=third.restart(false,None); let mut verified=Recovery::new(&log,&commit).unwrap();let homes=fourth.live; assert_eq!(verified.replay(&mut fourth,&homes),Ok(Outcome::AlreadyApplied)); assert_eq!(fourth.operations,0); assert_eq!(&fourth.durable,verified.expected()); double_cuts+=1; } } } } assert_eq!(double_cuts,34); } #[test] fn os_turkfs_recovery_second_cut_negative() { let (log,commit,mut disk)=setup();disk.cut_after=Some(1); let mut old_process=Recovery::new(&log,&commit).unwrap();let old_view=disk.live; assert_eq!(old_process.replay(&mut disk,&old_view),Err(Error::Io)); let operations=disk.operations; assert_eq!(old_process.replay(&mut disk,&old_view),Err(Error::Terminal)); assert_eq!(disk.operations,operations); // This exercises a stale object's refusal. Real process/cache identity is // a separate host evidence gate; this in-memory model cannot establish it. let mut restarted=disk.restart(true,None); let mut corrupt=restarted.live;corrupt[1][511]^=1; let mut new_process=Recovery::new(&log,&commit).unwrap(); assert_eq!(new_process.replay(&mut restarted,&corrupt),Err(Error::UnexpectedHome)); assert_eq!(restarted.operations,0); let mut missing_commit=Recovery::new(&log,&[0;512]).unwrap();let homes=restarted.live; assert_eq!(missing_commit.replay(&mut restarted,&homes),Err(Error::UncommittedHome)); assert_eq!(restarted.operations,0); }