feat: добавить новые тесты для обработки не-NRes байтов и минимальной структуры архива
This commit was merged in pull request #3.
This commit is contained in:
@@ -73,6 +73,14 @@ fn panic_message(payload: Box<dyn Any + Send>) -> String {
|
|||||||
String::from("panic without message")
|
String::from("panic without message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_u32_le(bytes: &[u8], offset: usize) -> u32 {
|
||||||
|
let slice = bytes
|
||||||
|
.get(offset..offset + 4)
|
||||||
|
.expect("u32 read out of bounds in test");
|
||||||
|
let arr: [u8; 4] = slice.try_into().expect("u32 conversion failed in test");
|
||||||
|
u32::from_le_bytes(arr)
|
||||||
|
}
|
||||||
|
|
||||||
fn build_nres_bytes(entries: &[SyntheticEntry<'_>]) -> Vec<u8> {
|
fn build_nres_bytes(entries: &[SyntheticEntry<'_>]) -> Vec<u8> {
|
||||||
let mut out = vec![0u8; 16];
|
let mut out = vec![0u8; 16];
|
||||||
let mut offsets = Vec::with_capacity(entries.len());
|
let mut offsets = Vec::with_capacity(entries.len());
|
||||||
@@ -300,6 +308,203 @@ fn nres_raw_mode_exposes_whole_file() {
|
|||||||
assert_eq!(data.as_slice(), original.as_slice());
|
assert_eq!(data.as_slice(), original.as_slice());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nres_raw_mode_accepts_non_nres_bytes() {
|
||||||
|
let payload = b"not-an-nres-archive".to_vec();
|
||||||
|
let bytes: Arc<[u8]> = Arc::from(payload.clone().into_boxed_slice());
|
||||||
|
|
||||||
|
match Archive::open_bytes(bytes.clone(), OpenOptions::default()) {
|
||||||
|
Err(Error::InvalidMagic { .. }) => {}
|
||||||
|
other => panic!("expected InvalidMagic without raw_mode, got {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let archive = Archive::open_bytes(
|
||||||
|
bytes,
|
||||||
|
OpenOptions {
|
||||||
|
raw_mode: true,
|
||||||
|
sequential_hint: false,
|
||||||
|
prefetch_pages: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("raw_mode should accept any bytes");
|
||||||
|
|
||||||
|
assert_eq!(archive.entry_count(), 1);
|
||||||
|
assert_eq!(archive.find("raw"), Some(EntryId(0)));
|
||||||
|
assert_eq!(
|
||||||
|
archive
|
||||||
|
.read(EntryId(0))
|
||||||
|
.expect("raw read failed")
|
||||||
|
.as_slice(),
|
||||||
|
payload.as_slice()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nres_open_options_hints_do_not_change_payload() {
|
||||||
|
let payload: Vec<u8> = (0..70_000u32).map(|v| (v % 251) as u8).collect();
|
||||||
|
let src = build_nres_bytes(&[SyntheticEntry {
|
||||||
|
kind: 7,
|
||||||
|
attr1: 70,
|
||||||
|
attr2: 700,
|
||||||
|
attr3: 7000,
|
||||||
|
name: "big.bin",
|
||||||
|
data: &payload,
|
||||||
|
}]);
|
||||||
|
let arc: Arc<[u8]> = Arc::from(src.into_boxed_slice());
|
||||||
|
|
||||||
|
let baseline = Archive::open_bytes(arc.clone(), OpenOptions::default())
|
||||||
|
.expect("baseline open should succeed");
|
||||||
|
let hinted = Archive::open_bytes(
|
||||||
|
arc,
|
||||||
|
OpenOptions {
|
||||||
|
raw_mode: false,
|
||||||
|
sequential_hint: true,
|
||||||
|
prefetch_pages: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("open with hints should succeed");
|
||||||
|
|
||||||
|
assert_eq!(baseline.entry_count(), 1);
|
||||||
|
assert_eq!(hinted.entry_count(), 1);
|
||||||
|
assert_eq!(baseline.find("BIG.BIN"), Some(EntryId(0)));
|
||||||
|
assert_eq!(hinted.find("big.bin"), Some(EntryId(0)));
|
||||||
|
assert_eq!(
|
||||||
|
baseline
|
||||||
|
.read(EntryId(0))
|
||||||
|
.expect("baseline read failed")
|
||||||
|
.as_slice(),
|
||||||
|
hinted
|
||||||
|
.read(EntryId(0))
|
||||||
|
.expect("hinted read failed")
|
||||||
|
.as_slice()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nres_commit_empty_archive_has_minimal_layout() {
|
||||||
|
let mut path = std::env::temp_dir();
|
||||||
|
path.push(format!(
|
||||||
|
"nres-empty-commit-{}-{}.lib",
|
||||||
|
std::process::id(),
|
||||||
|
unix_time_nanos()
|
||||||
|
));
|
||||||
|
fs::write(&path, build_nres_bytes(&[])).expect("write empty archive failed");
|
||||||
|
|
||||||
|
Archive::edit_path(&path)
|
||||||
|
.expect("edit_path failed for empty archive")
|
||||||
|
.commit()
|
||||||
|
.expect("commit failed for empty archive");
|
||||||
|
|
||||||
|
let bytes = fs::read(&path).expect("failed to read committed archive");
|
||||||
|
assert_eq!(bytes.len(), 16, "empty archive must contain only header");
|
||||||
|
assert_eq!(&bytes[0..4], b"NRes");
|
||||||
|
assert_eq!(read_u32_le(&bytes, 4), 0x100);
|
||||||
|
assert_eq!(read_u32_le(&bytes, 8), 0);
|
||||||
|
assert_eq!(read_u32_le(&bytes, 12), 16);
|
||||||
|
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nres_commit_recomputes_header_directory_and_sort_table() {
|
||||||
|
let mut path = std::env::temp_dir();
|
||||||
|
path.push(format!(
|
||||||
|
"nres-commit-layout-{}-{}.lib",
|
||||||
|
std::process::id(),
|
||||||
|
unix_time_nanos()
|
||||||
|
));
|
||||||
|
fs::write(&path, build_nres_bytes(&[])).expect("write empty archive failed");
|
||||||
|
|
||||||
|
let mut editor = Archive::edit_path(&path).expect("edit_path failed");
|
||||||
|
editor
|
||||||
|
.add(NewEntry {
|
||||||
|
kind: 10,
|
||||||
|
attr1: 1,
|
||||||
|
attr2: 2,
|
||||||
|
attr3: 3,
|
||||||
|
name: "Zulu",
|
||||||
|
data: b"aaaaa",
|
||||||
|
})
|
||||||
|
.expect("add #0 failed");
|
||||||
|
editor
|
||||||
|
.add(NewEntry {
|
||||||
|
kind: 11,
|
||||||
|
attr1: 4,
|
||||||
|
attr2: 5,
|
||||||
|
attr3: 6,
|
||||||
|
name: "alpha",
|
||||||
|
data: b"bbbbbbbb",
|
||||||
|
})
|
||||||
|
.expect("add #1 failed");
|
||||||
|
editor
|
||||||
|
.add(NewEntry {
|
||||||
|
kind: 12,
|
||||||
|
attr1: 7,
|
||||||
|
attr2: 8,
|
||||||
|
attr3: 9,
|
||||||
|
name: "Beta",
|
||||||
|
data: b"cccc",
|
||||||
|
})
|
||||||
|
.expect("add #2 failed");
|
||||||
|
editor.commit().expect("commit failed");
|
||||||
|
|
||||||
|
let bytes = fs::read(&path).expect("failed to read committed archive");
|
||||||
|
assert_eq!(&bytes[0..4], b"NRes");
|
||||||
|
assert_eq!(read_u32_le(&bytes, 4), 0x100);
|
||||||
|
|
||||||
|
let entry_count = usize::try_from(read_u32_le(&bytes, 8)).expect("entry_count overflow");
|
||||||
|
let total_size = usize::try_from(read_u32_le(&bytes, 12)).expect("total_size overflow");
|
||||||
|
assert_eq!(entry_count, 3);
|
||||||
|
assert_eq!(total_size, bytes.len());
|
||||||
|
|
||||||
|
let directory_offset = total_size
|
||||||
|
.checked_sub(entry_count * 64)
|
||||||
|
.expect("invalid directory offset");
|
||||||
|
assert!(directory_offset >= 16);
|
||||||
|
|
||||||
|
let mut sort_indices = Vec::new();
|
||||||
|
let mut prev_data_end = 16usize;
|
||||||
|
for idx in 0..entry_count {
|
||||||
|
let base = directory_offset + idx * 64;
|
||||||
|
let data_size = usize::try_from(read_u32_le(&bytes, base + 12)).expect("size overflow");
|
||||||
|
let data_offset = usize::try_from(read_u32_le(&bytes, base + 56)).expect("offset overflow");
|
||||||
|
let sort_index =
|
||||||
|
usize::try_from(read_u32_le(&bytes, base + 60)).expect("sort index overflow");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
data_offset % 8,
|
||||||
|
0,
|
||||||
|
"entry #{idx} data offset must be 8-byte aligned"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
data_offset >= prev_data_end,
|
||||||
|
"entry #{idx} offset regressed"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
data_offset + data_size <= directory_offset,
|
||||||
|
"entry #{idx} overlaps directory"
|
||||||
|
);
|
||||||
|
prev_data_end = data_offset + data_size;
|
||||||
|
sort_indices.push(sort_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
let names = ["Zulu", "alpha", "Beta"];
|
||||||
|
let mut expected_sort: Vec<usize> = (0..names.len()).collect();
|
||||||
|
expected_sort
|
||||||
|
.sort_by(|a, b| cmp_name_case_insensitive(names[*a].as_bytes(), names[*b].as_bytes()));
|
||||||
|
assert_eq!(
|
||||||
|
sort_indices, expected_sort,
|
||||||
|
"sort table must contain original indexes in case-insensitive alphabetical order"
|
||||||
|
);
|
||||||
|
|
||||||
|
let archive = Archive::open_path(&path).expect("re-open failed");
|
||||||
|
assert_eq!(archive.find("zulu"), Some(EntryId(0)));
|
||||||
|
assert_eq!(archive.find("ALPHA"), Some(EntryId(1)));
|
||||||
|
assert_eq!(archive.find("beta"), Some(EntryId(2)));
|
||||||
|
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nres_synthetic_read_find_and_edit() {
|
fn nres_synthetic_read_find_and_edit() {
|
||||||
let payload_a = b"alpha";
|
let payload_a = b"alpha";
|
||||||
|
|||||||
Reference in New Issue
Block a user