Files
guara/crates/guara-core/tests/raid.rs
T
Lucas RayanandClaude Sonnet 5 6328220c60
CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s
chore(i18n): finish translating code comments and scripts to English
Completes the English-only standardization for code, comments, scripts
and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en,
untouched). Covers the remaining crates/, apps/gui/ui/ and
apps/gui/src-tauri/ comments, .gitea/ workflow and template comments,
.githooks/ hooks, and the scripts/ source comments for the files
already renamed in a prior commit (check.sh, generate-logo.py,
install-hooks.sh).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
2026-10-30 20:43:02 -03:00

281 lines
8.7 KiB
Rust

//! Software RAID tests: arrays built by independent stripe encoders and read back
//! intact, degraded, and via Linux `mdadm` v1.x and v0.90 superblocks.
use guara_core::raid::*;
use guara_core::source::{read_vec, MemSource, Source};
use std::sync::Arc;
const C: usize = 4096;
fn data(len: usize) -> Vec<u8> {
let mut x = 0x1234_5678_9ABC_DEF1u64;
(0..len)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x >> 24) as u8
})
.collect()
}
fn gmul(mut a: u8, mut b: u8) -> u8 {
let mut r = 0;
while b != 0 {
if b & 1 != 0 {
r ^= a;
}
let hi = a & 0x80;
a <<= 1;
if hi != 0 {
a ^= 0x1d;
}
b >>= 1;
}
r
}
fn gpow(k: usize) -> u8 {
(0..k).fold(1, |a, _| gmul(a, 2))
}
/// Builds member disk buffers for a RAID 5/6 array using the specified parity layout.
fn encode_parity(level: Level, layout: Layout, n: usize, logical: &[u8]) -> Vec<Vec<u8>> {
let dd = if level == Level::R6 { n - 2 } else { n - 1 };
let rows = logical.len() / (C * dd);
let mut disks = vec![vec![0u8; rows * C]; n];
for row in 0..rows {
let r = row % n;
let (pd, qd) = match layout {
Layout::LeftSymmetric | Layout::LeftAsymmetric => {
let p = n - 1 - r;
(p, (p + 1) % n)
}
_ => (r, (r + 1) % n),
};
let sym = matches!(layout, Layout::LeftSymmetric | Layout::RightSymmetric);
// data disk slot order for this stripe row
let order: Vec<usize> = if sym {
let start = if level == Level::R6 { qd + 1 } else { pd + 1 };
(0..dd).map(|d| (start + d) % n).collect()
} else {
(0..n)
.filter(|&i| i != pd && !(level == Level::R6 && i == qd))
.collect()
};
let mut p = vec![0u8; C];
let mut q = vec![0u8; C];
for (k, &disk) in order.iter().enumerate() {
let src = &logical[(row * dd + k) * C..(row * dd + k + 1) * C];
disks[disk][row * C..(row + 1) * C].copy_from_slice(src);
for i in 0..C {
p[i] ^= src[i];
q[i] ^= gmul(gpow(k), src[i]);
}
}
disks[pd][row * C..(row + 1) * C].copy_from_slice(&p);
if level == Level::R6 {
disks[qd][row * C..(row + 1) * C].copy_from_slice(&q);
}
}
disks
}
fn members(imgs: &[Vec<u8>], missing: &[usize]) -> Vec<Option<Member>> {
imgs.iter()
.enumerate()
.map(|(i, v)| {
(!missing.contains(&i)).then(|| Member {
src: Arc::new(MemSource(v.clone())),
data_offset: 0,
})
})
.collect()
}
fn check(level: Level, layout: Layout, n: usize, missing: &[usize]) {
let dd = if level == Level::R6 { n - 2 } else { n - 1 };
let logical = data(C * dd * 23);
let imgs = encode_parity(level, layout, n, &logical);
let r = RaidSource::new(RaidParams::simple(
level,
C as u64,
layout,
members(&imgs, missing),
))
.unwrap();
assert_eq!(r.size(), logical.len() as u64);
let got = read_vec(&r, 0, logical.len()).unwrap();
assert!(
got == logical,
"{level:?} {layout:?} n={n} faltando {missing:?}"
);
// unaligned cross-stripe read
let part = read_vec(&r, 5000, 30000).unwrap();
assert_eq!(part, &logical[5000..35000]);
}
#[test]
fn raid5_all_layouts_intact_and_degraded() {
for layout in Layout::all_raid5() {
for n in [3, 4, 5] {
check(Level::R5, layout, n, &[]);
for m in 0..n {
check(Level::R5, layout, n, &[m]);
}
}
}
}
#[test]
fn raid6_layouts_with_one_and_two_missing() {
for layout in Layout::all_raid5() {
for n in [4, 5, 6] {
check(Level::R6, layout, n, &[]);
for a in 0..n {
check(Level::R6, layout, n, &[a]);
for b in a + 1..n {
check(Level::R6, layout, n, &[a, b]);
}
}
}
}
}
#[test]
fn raid0_raid1_linear_and_raid10_near() {
let logical = data(C * 24);
// 3-disk RAID 0
let mut imgs = vec![vec![]; 3];
for (c, ch) in logical.chunks(C).enumerate() {
imgs[c % 3].extend_from_slice(ch);
}
let r = RaidSource::new(RaidParams::simple(
Level::R0,
C as u64,
Layout::LeftSymmetric,
members(&imgs, &[]),
))
.unwrap();
assert_eq!(read_vec(&r, 0, logical.len()).unwrap(), logical);
// 2-disk RAID 1 with first mirror missing
let m = vec![logical.clone(), logical.clone()];
let r = RaidSource::new(RaidParams::simple(
Level::R1,
C as u64,
Layout::LeftSymmetric,
members(&m, &[0]),
))
.unwrap();
assert_eq!(read_vec(&r, 0, logical.len()).unwrap(), logical);
// Linear
let (a, b) = logical.split_at(C * 10);
let r = RaidSource::new(RaidParams::simple(
Level::Linear,
C as u64,
Layout::LeftSymmetric,
members(&[a.to_vec(), b.to_vec()], &[]),
))
.unwrap();
assert_eq!(read_vec(&r, 0, logical.len()).unwrap(), logical);
// 4-disk RAID 10 (`near = 2`): sequential slot pairs.
let n = 4;
let mut imgs = vec![vec![0u8; C * 12]; n];
for (c, ch) in logical.chunks(C).enumerate() {
for j in 0..2 {
let slot = c * 2 + j;
let (disk, row) = (slot % n, slot / n);
imgs[disk][row * C..(row + 1) * C].copy_from_slice(ch);
}
}
let mut p = RaidParams::simple(
Level::R10,
C as u64,
Layout::LeftSymmetric,
members(&imgs, &[1]),
);
p.near = 2;
p.far = 1;
let r = RaidSource::new(p).unwrap();
assert_eq!(r.size(), (C * 24) as u64);
assert_eq!(read_vec(&r, 0, logical.len()).unwrap(), logical);
}
#[allow(clippy::too_many_arguments)]
fn md_v1_super(
uuid: u8,
level: u32,
layout: u32,
raid_disks: u32,
dev_number: u32,
role: u16,
data_off_sectors: u64,
data_sectors: u64,
events: u64,
) -> Vec<u8> {
let mut b = vec![0u8; 4096];
b[0..4].copy_from_slice(&0xA92B_4EFCu32.to_le_bytes());
b[4..8].copy_from_slice(&1u32.to_le_bytes());
b[16..32].fill(uuid);
b[32..36].copy_from_slice(b"test");
b[72..76].copy_from_slice(&level.to_le_bytes());
b[76..80].copy_from_slice(&layout.to_le_bytes());
b[80..88].copy_from_slice(&data_sectors.to_le_bytes());
b[88..92].copy_from_slice(&((C / 512) as u32).to_le_bytes());
b[92..96].copy_from_slice(&raid_disks.to_le_bytes());
b[128..136].copy_from_slice(&data_off_sectors.to_le_bytes());
b[136..144].copy_from_slice(&data_sectors.to_le_bytes());
b[160..164].copy_from_slice(&dev_number.to_le_bytes());
b[200..208].copy_from_slice(&events.to_le_bytes());
b[220..224].copy_from_slice(&raid_disks.to_le_bytes());
for i in 0..raid_disks as usize {
b[256 + i * 2..258 + i * 2].copy_from_slice(&(i as u16).to_le_bytes());
}
let _ = role;
b
}
#[test]
fn assembles_from_md_v12_superblocks_with_a_missing_disk() {
let n = 4;
let logical = data(C * 3 * 40);
let imgs = encode_parity(Level::R5, Layout::LeftSymmetric, n, &logical);
let data_off = 2048u64; // 1 MiB in 512-byte sectors
let mut members_in = vec![];
for (i, im) in imgs.iter().enumerate() {
if i == 2 {
continue; // simulate missing disk 2
}
let mut full = vec![0u8; 4096 + (data_off as usize) * 512 + im.len()];
let sb = md_v1_super(
7,
5,
2,
n as u32,
i as u32,
i as u16,
data_off,
(im.len() / 512) as u64,
100,
);
full[4096..8192].copy_from_slice(&sb);
full[4096 + data_off as usize * 512..][..im.len()].copy_from_slice(im);
// mdadm v1.2 superblock sits at 4 KiB; array data starts at `data_offset` sectors
let mut real = vec![0u8; data_off as usize * 512 + im.len()];
real[4096..8192].copy_from_slice(&sb);
real[data_off as usize * 512..].copy_from_slice(im);
let src: Arc<dyn Source> = Arc::new(MemSource(real));
let sup = read_md_super(src.as_ref()).expect("superbloco");
assert_eq!(sup.version, "1.2");
assert_eq!(sup.role, Some(i as u32));
members_in.push((src, sup));
}
let arrays = assemble_md(members_in);
assert_eq!(arrays.len(), 1);
let a = arrays.into_iter().next().unwrap().unwrap();
assert_eq!(a.missing, 1);
assert_eq!(a.info.level, Level::R5);
assert_eq!(a.source.size(), logical.len() as u64);
assert!(read_vec(a.source.as_ref(), 0, logical.len()).unwrap() == logical);
}