test(ecstore): complete EC validation coverage gate

* test(ecstore): complete EC validation coverage gate

* test(ecstore): stabilize validation suite after rebase

* test(ecstore): fix rio-v2 clippy lint
This commit is contained in:
Zhengchao An
2026-07-09 03:12:48 +08:00
committed by GitHub
parent 7c701d9f2c
commit ed81d2f6b8
18 changed files with 4916 additions and 264 deletions
+101
View File
@@ -659,6 +659,76 @@ mod tests {
assert!(err.to_string().contains("inconsistent read source shards"));
}
#[test]
fn rustfs_codec_decode_engine_rebuilds_missing_parity_for_source_verification() {
let erasure = Erasure::new(2, 3, 32);
let encoded = erasure
.encode_data(&(0u8..64u8).collect::<Vec<_>>())
.expect("test stripe should encode");
let mut shards = encoded.into_iter().map(|shard| Some(shard.to_vec())).collect::<Vec<_>>();
shards[0] = None;
let missing_parity_index = erasure.data_shards + erasure.parity_shards - 1;
shards[missing_parity_index] = None;
let engine = RustfsCodecDecodeEngine::new(&erasure).expect("engine should be created");
let mut workspace = engine
.prepare_workspace(erasure.shard_size())
.expect("workspace should be prepared");
let outcome = engine
.reconstruct_into(&mut shards, &mut workspace)
.expect("missing data plus missing parity should be recoverable");
assert_eq!(outcome, GET_RECONSTRUCT_OUTCOME_RUSTFS_CALLED);
assert!(shards[0].is_some());
assert!(shards[missing_parity_index].is_some());
}
#[test]
fn rustfs_codec_decode_engine_rejects_invalid_empty_payload_shape() {
let erasure = Erasure::new(2, 2, 16);
let engine = RustfsCodecDecodeEngine::new(&erasure).expect("engine should be created");
let mut workspace = engine.prepare_workspace(0).expect("workspace should be prepared");
let mut shards = vec![Some(Vec::new()), None, Some(Vec::new())];
let err = engine
.reconstruct_into(&mut shards, &mut workspace)
.expect_err("invalid empty payload shard count must fail closed");
assert!(err.to_string().contains("invalid shard count"));
}
#[test]
fn rustfs_codec_decode_engine_skips_empty_payload_when_enough_empty_sources_exist() {
let erasure = Erasure::new(3, 2, 16);
let engine = RustfsCodecDecodeEngine::new(&erasure).expect("engine should be created");
let mut workspace = engine.prepare_workspace(0).expect("workspace should be prepared");
let mut shards = vec![Some(Vec::new()), None, Some(Vec::new()), Some(Vec::new()), None];
let outcome = engine
.reconstruct_into(&mut shards, &mut workspace)
.expect("empty payload should be restored without codec allocation");
assert_eq!(outcome, GET_RECONSTRUCT_OUTCOME_SKIP_EMPTY_PAYLOAD);
assert_eq!(shards[1], Some(Vec::new()));
assert!(!engine.codec_is_initialized());
}
#[test]
fn rustfs_codec_decode_engine_returns_called_without_parity_codec() {
let erasure = Erasure::new(2, 0, 16);
let engine = RustfsCodecDecodeEngine::new(&erasure).expect("engine should be created");
let mut workspace = engine.prepare_workspace(4).expect("workspace should be prepared");
let mut shards = vec![None, Some(vec![2, 2, 2, 2])];
let outcome = engine
.reconstruct_into(&mut shards, &mut workspace)
.expect("zero parity engine should not allocate codec");
assert_eq!(outcome, GET_RECONSTRUCT_OUTCOME_RUSTFS_CALLED);
assert!(shards[0].is_none());
assert!(!engine.codec_is_initialized());
}
#[test]
fn rustfs_codec_decode_engine_recovers_empty_data_shard() {
let erasure = Erasure::new(4, 2, 16);
@@ -672,6 +742,34 @@ mod tests {
assert!(shards.iter().take(erasure.data_shards).all(Option::is_some));
}
#[test]
fn codec_streaming_decode_engine_dispatches_to_legacy_workspace_and_rejects_mismatch() {
let erasure = Erasure::new(2, 2, 16);
let mut shards = encoded_shards(&erasure, b"legacy enum dispatch");
shards[0] = None;
let engine = CodecStreamingDecodeEngine::legacy(erasure);
assert_eq!(engine.data_shards(), 2);
assert_eq!(engine.parity_shards(), 2);
assert_eq!(engine.block_size(), 16);
assert_eq!(engine.engine_name(), GET_CODEC_STREAMING_ENGINE_LEGACY);
assert!(!engine.supports_progressive_decode());
assert!(!engine.supports_aligned_shards());
let mut workspace = engine.prepare_workspace(8).expect("workspace should be prepared");
assert_eq!(workspace.shard_len(), 8);
engine
.reconstruct_into(&mut shards, &mut workspace)
.expect("legacy enum engine should dispatch reconstruction");
assert!(shards.iter().take(engine.data_shards()).all(Option::is_some));
let rustfs = CodecStreamingDecodeEngine::rustfs(&Erasure::new(2, 2, 16)).expect("engine should be created");
let err = rustfs
.reconstruct_into(&mut shards, &mut workspace)
.expect_err("engine/workspace variant mismatch must fail");
assert!(err.to_string().contains("engine/workspace mismatch"));
}
#[test]
fn codec_streaming_decode_engine_dispatches_to_rustfs_workspace() {
let erasure = Erasure::new(4, 2, 16);
@@ -680,6 +778,9 @@ mod tests {
let engine = CodecStreamingDecodeEngine::rustfs(&erasure).expect("engine should be created");
let mut workspace = engine.prepare_workspace(4).expect("workspace should be prepared");
if let CodecStreamingDecodeWorkspace::Rustfs(workspace) = &workspace {
assert_eq!(<RustfsCodecDecodeWorkspace as DecodeWorkspace>::shard_len(workspace), 4);
}
engine
.reconstruct_into(&mut shards, &mut workspace)
.expect("enum engine should dispatch reconstruction");
@@ -102,4 +102,17 @@ mod tests {
assert_eq!(buf.len(), 4);
assert_eq!(pool.buffers.len(), 4);
}
#[test]
fn workspace_reports_shard_len_and_pool_reserves_when_reused_slot_is_too_small() {
let workspace = RustfsCodecDecodeWorkspace::new(37);
assert_eq!(workspace.shard_len(), 37);
let mut pool = ShardBufferPool::new(1);
pool.put(0, Vec::with_capacity(2));
let grown = pool.take(0, 8);
assert_eq!(grown.len(), 8);
assert!(grown.capacity() >= 8);
}
}