#[path = "../../../os-turkfs-journal/src/checkpoint.rs"] mod checkpoint; use checkpoint::{Block, Checkpoint, Device, Error, Mount, State, HOMES, LOG, COMMIT, CHECKPOINT}; #[derive(Clone)] struct Disk { live: [Block; 7], durable: [Block; 7], operations: Vec<(u64, bool)>, fail: Option, } fn slot(lba: u64) -> usize { [111, LOG, COMMIT, CHECKPOINT, 120, 128, 136].iter().position(|v| *v == lba).unwrap() } impl Device for Disk { fn write(&mut self, lba: u64, bytes: &Block) -> Result<(), ()> { self.operations.push((lba, false)); if self.fail == Some(self.operations.len()) { return Err(()); } self.live[slot(lba)] = *bytes; Ok(()) } fn flush(&mut self) -> Result<(), ()> { self.operations.push((0, true)); if self.fail == Some(self.operations.len()) { return Err(()); } self.durable = self.live; Ok(()) } } fn setup() -> (Checkpoint, 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 job = Checkpoint::new(&log, &commit).unwrap(); let live = [[0;512], log, commit, [0;512], [0x44;512], [0x66;512], [0x77;512]]; (job, Disk {live, durable: live, operations: Vec::new(), fail: None}) } fn mounted(job: &Checkpoint, blocks: &[Block;7]) -> Result { job.mount(&blocks[1], &blocks[2], &blocks[3], &[blocks[4], blocks[5], blocks[6]]) } #[test] fn os_turkfs_journal_checkpoint_positive() { let (mut job, mut disk) = setup(); assert_eq!(mounted(&job, &disk.durable), Ok(Mount::Committed)); job.apply(&mut disk).unwrap(); assert_eq!(job.state(), State::Applied); assert_ne!(disk.durable[slot(COMMIT)], [0;512]); assert_eq!(mounted(&job, &disk.durable), Ok(Mount::Committed)); // A new object has no knowledge of the previous process's state. It must // rewrite and flush homes before persisting the checkpoint. let (mut restarted, _) = setup(); restarted.apply(&mut disk).unwrap(); restarted.persist(&mut disk).unwrap(); assert_eq!(mounted(&restarted, &disk.durable), Ok(Mount::Checkpointed)); restarted.recycle(&mut disk).unwrap(); assert_eq!(restarted.state(), State::Reusable); assert_eq!(mounted(&restarted, &disk.durable), Ok(Mount::Clean)); assert_eq!(&disk.durable[4..], restarted.homes()); assert_eq!(disk.durable[3], *restarted.image()); assert_eq!(&disk.operations[4..], &[(120,false),(128,false),(136,false),(0,true),(114,false),(0,true),(113,false),(112,false),(0,true)]); assert_eq!(HOMES, [120,128,136]); } #[test] fn os_turkfs_journal_checkpoint_negative() { let (mut job, mut disk) = setup(); assert_eq!(job.recycle(&mut disk), Err(Error::Order)); assert_eq!(job.persist(&mut disk), Err(Error::Order)); assert!(disk.operations.is_empty()); job.apply(&mut disk).unwrap(); let before = disk.live; assert_eq!(job.recycle(&mut disk), Err(Error::Order)); assert_eq!(disk.live, before); for fail in 1..=9 { let (mut interrupted, mut disk) = setup(); disk.fail = Some(fail); let result = interrupted.apply(&mut disk).and_then(|()| interrupted.persist(&mut disk)) .and_then(|()| interrupted.recycle(&mut disk)); assert_eq!(result, Err(Error::Io), "failure boundary {fail}"); assert_eq!(interrupted.state(), State::Failed); assert_eq!(disk.operations.len(), fail); assert_eq!(interrupted.recycle(&mut disk), Err(Error::Order)); // Exercise both loss of all unflushed writes and survival of those // writes. Neither endpoint may classify an unapplied home as clean. for state in [&disk.live, &disk.durable] { let result = mounted(&interrupted, state).unwrap(); if result != Mount::Committed { assert_eq!(&state[4..], interrupted.homes()); } } } let (mut job, mut disk) = setup(); job.apply(&mut disk).unwrap(); job.persist(&mut disk).unwrap(); disk.live[slot(128)][0] ^= 1; assert_eq!(mounted(&job, &disk.live), Err(Error::HomeMismatch)); disk.live[slot(128)][0] ^= 1; disk.live[slot(CHECKPOINT)][20] ^= 1; assert_eq!(mounted(&job, &disk.live), Err(Error::Format)); let (job, mut disk) = setup(); disk.live[slot(COMMIT)][8] ^= 1; assert_eq!(mounted(&job, &disk.live), Err(Error::Format)); }