rust volume: regression test for makeup_diff replay across a 32 GiB offset boundary (#11410) (#11427)

* rust volume: test makeup_diff replay across a 32 GiB offset boundary

Issue #11410 corrupted a replayed write's index offset in Go's makeupDiff
by patching only four of the five offset bytes. The Rust makeup_diff
already encodes the whole offset through idx_entry_to_bytes and
Offset::from_actual_offset; this adds the mirror of
TestConcurrentWriteCrossesOffsetBoundary so a regression would fail here
the same way it does under -tags=5BytesOffset on the Go side.

Sparse-truncate the .dat to 64 GiB, compact, write, commit: the index
offset must equal the .cpd size and the needle must stay readable
through a second vacuum. Gated on the 5bytes feature since a 64 GiB
.dat exceeds the 32 GiB range of 4-byte offsets.

* rust volume: skip the offset-boundary replay test on Windows

Windows set_len allocates the full 64 GiB extension instead of a sparse
range, so the test fails with StorageFull on CI runners. Gate it to unix,
where set_len leaves the extension unallocated.
This commit is contained in:
Chris Lu
2026-09-23 18:08:20 +08:00
committed by GitHub
parent 196c71b613
commit d848b8ed00
+56
View File
@@ -7001,6 +7001,62 @@ mod tests {
assert_eq!(got.data, b"late-write");
}
/// Mirrors Go's TestConcurrentWriteCrossesOffsetBoundary (issue #11410):
/// a replayed write's index entry must encode all offset bytes even when
/// old and new offsets sit in different 32 GiB ranges. Unix-only: Windows
/// set_len eagerly allocates the 64 GiB extension.
#[test]
#[cfg(all(unix, feature = "5bytes"))]
fn test_makeup_diff_replay_crosses_offset_boundary() {
let tmp = TempDir::new().unwrap();
let dir = tmp.path().to_str().unwrap();
let mut v = make_test_volume(dir);
write_test_needle(&mut v, 1, &[b'x'; 32]);
v.dat_file.as_ref().unwrap().set_len(64u64 << 30).unwrap();
v.compact_by_index(0, 0, |_| true).unwrap();
let late_data = vec![b'x'; 113];
write_test_needle(&mut v, 342511246, &late_data);
let mut before = Needle {
id: NeedleId(342511246),
..Needle::default()
};
v.read_needle(&mut before).unwrap();
assert_eq!(before.data, late_data);
let expected_offset = fs::metadata(v.file_name(".cpd")).unwrap().len() as i64;
v.commit_compact().unwrap();
let mut idx_file = File::open(v.file_name(".idx")).unwrap();
let mut actual_offset = 0i64;
crate::storage::idx::walk_index_file(&mut idx_file, 0, |key, offset, _size| {
if key == NeedleId(342511246) {
actual_offset = offset.to_actual_offset();
}
Ok(())
})
.unwrap();
assert_eq!(actual_offset, expected_offset);
let mut after = Needle {
id: NeedleId(342511246),
..Needle::default()
};
v.read_needle(&mut after).unwrap();
assert_eq!(after.data, late_data);
v.compact_by_index(0, 0, |_| true).unwrap();
v.commit_compact().unwrap();
let mut third = Needle {
id: NeedleId(342511246),
..Needle::default()
};
v.read_needle(&mut third).unwrap();
assert_eq!(third.data, late_data);
}
/// Vacuum compaction must tolerate an .idx entry whose offset points past
/// the end of the .dat file (the failure mode in issue #8928). The bad
/// entry is silently dropped from the resulting .cpx; healthy needles