use std::{
fs::{remove_file, write},
path::Path,
};
use aok::{OK, Void};
use compio::runtime::Runtime;
use log::info;
use tempfile::tempdir;
use wdev::{Device, Error, SegmentedDevice};
use wram::AlignedBuf;
use crate::support::make_pattern_data;
#[test]
fn recovery_matching_segment_size_succeeds() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let seg_size: u64 = 64 * 1024;
let db_path = dir.path().join("recover_match.log");
let pattern = make_pattern_data(4096, 29, 5);
{
let device = SegmentedDevice::segmented(&db_path, seg_size)?;
for seg_id in 0..3u32 {
let buf = if seg_id == 2 {
AlignedBuf::from_slice(&pattern[..], 4096)?
} else {
AlignedBuf::from_slice(&[(seg_id * 11 + 3) as u8; 4096], 4096)?
};
let (res, _) = device.write_aligned((seg_id as u64) * seg_size, buf).await;
assert_eq!(res?, 4096);
}
assert_eq!(device.end_segment(), Some(2));
}
{
let device = SegmentedDevice::segmented(&db_path, seg_size)?;
device.recover()?;
assert_eq!(
device.start_segment(),
0,
"连续段恢复 start_segment 应保持 0"
);
assert_eq!(
device.end_segment(),
Some(2),
"end_segment 应恢复为最大连续段号"
);
let check = AlignedBuf::new(4096, 4096)?;
let (res, check) = device.read_aligned(2 * seg_size, check).await;
assert_eq!(res?, 4096);
assert_eq!(check.as_slice(), &pattern[..]);
}
info!("同段尺寸重开恢复成功 (Recovery_MatchingSegmentSize_Succeeds)");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn recovery_larger_existing_segment_detects_mismatch() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let small_seg: u64 = 64 * 1024;
let big_seg: u64 = 256 * 1024;
let db_path = dir.path().join("recover_big.log");
{
let device = SegmentedDevice::segmented(&db_path, big_seg)?;
let buf = AlignedBuf::from_slice(&[0x5Au8; 256 * 1024], 4096)?;
let (res, _) = device.write_aligned(0, buf).await;
assert_eq!(res?, 256 * 1024);
}
let device = SegmentedDevice::segmented(&db_path, small_seg)?;
let recovered = device.recover();
assert!(
matches!(
recovered,
Err(Error::SegmentSizeMismatch {
segment: 0,
file_size: 262144,
segment_size: 65536
})
),
"已存在段文件超过配置段尺寸必须报 SegmentSizeMismatch,实际为 {recovered:?}"
);
info!("超大已存在段文件恢复检测通过 (Recovery_LargerExistingSegment_DetectsMismatch)");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn recovery_smaller_existing_segment_succeeds() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let small_seg: u64 = 64 * 1024;
let big_seg: u64 = 256 * 1024;
let db_path = dir.path().join("recover_small.log");
{
let device = SegmentedDevice::segmented(&db_path, small_seg)?;
let buf = AlignedBuf::from_slice(&[0x3Cu8; 4096], 4096)?;
let (res, _) = device.write_aligned(0, buf).await;
assert_eq!(res?, 4096);
}
let device = SegmentedDevice::segmented(&db_path, big_seg)?;
assert!(
device.recover().is_ok(),
"较小段文件应被放行 (SmallerExistingSegment)"
);
assert_eq!(device.end_segment(), Some(0));
info!("较小已存在段文件恢复放行通过 (Recovery_SmallerExistingSegment_Succeeds)");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn recover_files_restores_segment_range_after_gap() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let seg_size: u64 = 64 * 1024;
let db_path = dir.path().join("recover_gap.log");
{
let device = SegmentedDevice::segmented(&db_path, seg_size)?;
for seg_id in 0..3u32 {
let buf = AlignedBuf::from_slice(&[0xA5u8; 4096], 4096)?;
let (res, _) = device.write_aligned((seg_id as u64) * seg_size, buf).await;
assert_eq!(res?, 4096);
}
}
let device = SegmentedDevice::segmented(&db_path, seg_size)?;
remove_file(device.segment_path(0))?;
remove_file(device.segment_path(1))?;
write(&db_path, b"bare")?;
let mut txt_path = db_path.clone().into_os_string();
txt_path.push(".txt");
write(&txt_path, b"junk")?;
device.recover()?;
assert_eq!(device.start_segment(), 2, "空隙处应恢复 start_segment=2");
assert_eq!(
device.end_segment(),
None,
"无连续段时 end_segment 应为 None"
);
let check = AlignedBuf::new(4096, 4096)?;
let (res, _) = device.read_aligned(0, check).await;
assert!(
matches!(res, Err(Error::SegmentNotFound(0))),
"重启后访问已删段必须返回 SegmentNotFound,实际为 {res:?}"
);
assert!(
!device.segment_path(0).exists(),
"严禁幽灵重建已删除的段 0 文件"
);
info!("段号空隙恢复状态机校验通过 (LocalStorageDevice.RecoverFiles)");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn recovery_segment_numbering_stays_numeric_beyond_two_digits() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let seg_size: u64 = 64 * 1024;
let db_path = dir.path().join("numeric_order.log");
{
let device = SegmentedDevice::segmented(&db_path, seg_size)?;
for seg_id in 0..=12u32 {
let pattern = ((seg_id * 13 + 5) & 0xFF) as u8;
let buf = AlignedBuf::from_slice(&vec![pattern; 4096], 4096)?;
let (res, _) = device.write_aligned((seg_id as u64) * seg_size, buf).await;
assert_eq!(res?, 4096);
}
let mut plus7 = db_path.clone().into_os_string();
plus7.push(".+7");
write(Path::new(&plus7), b"junk")?;
device.sync().await?;
}
let device = SegmentedDevice::segmented(&db_path, seg_size)?;
for seg_id in 0..=9u32 {
remove_file(device.segment_path(seg_id))?;
}
device.recover()?;
assert_eq!(
device.start_segment(),
10,
"空隙处应按数值序恢复 start_segment=10,字典序 (.10 < .2) 不得干扰"
);
assert_eq!(
device.end_segment(),
Some(12),
"end_segment 应恢复为最大连续段号 12"
);
let check = AlignedBuf::new(4096, 4096)?;
let (res, _) = device.read_aligned(9 * seg_size, check).await;
assert!(
matches!(res, Err(Error::SegmentNotFound(9))),
"段 9 已删除,访问必须返回 SegmentNotFound,实际为 {res:?}"
);
assert!(
!device.segment_path(9).exists(),
"严禁幽灵重建已删除的段 9 文件"
);
for seg_id in 10..=12u32 {
let expected = ((seg_id * 13 + 5) & 0xFF) as u8;
let check = AlignedBuf::new(4096, 4096)?;
let (res, check) = device.read_aligned((seg_id as u64) * seg_size, check).await;
assert_eq!(res?, 4096);
assert!(
check.as_slice().iter().all(|&b| b == expected),
"段 {seg_id} 数据不匹配"
);
}
device.truncate_until_segment(12).await?;
assert_eq!(device.start_segment(), 12);
assert!(!device.segment_path(10).exists(), "段 10 必须已物理删除");
assert!(!device.segment_path(11).exists(), "段 11 必须已物理删除");
assert!(device.segment_path(12).exists(), "段 12 应保留");
let mut plus7 = db_path.clone().into_os_string();
plus7.push(".+7");
assert!(
Path::new(&plus7).exists(),
"符号前缀杂散文件不得被误认成段号而遭截断误删"
);
info!("段号数值序恢复与两位数段截断校验通过 (RecoverFiles numeric ordering)");
aok::Result::<()>::Ok(())
})?;
OK
}