const BOOT_SECTORS: &[u8] = include_bytes!("mkfs_sectors.bin");
use hadris_fat::{Error, FatVolume};
use std::io::Cursor;
#[test]
fn test_parse_boot_sector() {
let data = Cursor::new(BOOT_SECTORS.to_vec());
let result = FatVolume::open(data);
match result {
Ok(_fs) => {
}
Err(Error::Io(_)) => {
}
Err(Error::InvalidFsInfoSignature { .. }) => {
}
Err(e) => {
panic!("Unexpected error parsing boot sector: {e:?}");
}
}
}
#[test]
fn test_invalid_boot_signature() {
let mut data = BOOT_SECTORS.to_vec();
data[510] = 0x00; data[511] = 0x00;
let cursor = Cursor::new(data);
let result = FatVolume::open(cursor);
match result {
Err(Error::InvalidBootSignature { found }) => {
assert_eq!(found, 0x0000);
}
_ => panic!("Expected InvalidBootSignature error"),
}
}
#[test]
fn test_invalid_fat_count_rejected() {
for bad in [0u8, 3, 0xFF] {
let mut data = BOOT_SECTORS.to_vec();
data[16] = bad; match FatVolume::open(Cursor::new(data)) {
Err(Error::CorruptFilesystem { .. }) => {}
other => panic!("fat_count={bad} should be rejected, got {other:?}"),
}
}
}
#[test]
fn test_fat12_16_detection() {
let mut data = vec![0u8; 4096];
data[0] = 0xEB;
data[1] = 0x58;
data[2] = 0x90;
data[11] = 0x00;
data[12] = 0x02;
data[13] = 0x01;
data[14] = 0x01;
data[15] = 0x00;
data[16] = 0x02;
data[17] = 0x00; data[18] = 0x02;
data[19] = 0x00;
data[20] = 0x00;
data[21] = 0xF8;
data[22] = 0x01;
data[23] = 0x00;
data[32] = 0x40;
data[33] = 0x0B;
data[34] = 0x00;
data[35] = 0x00;
data[510] = 0x55;
data[511] = 0xAA;
let cursor = Cursor::new(data);
let result = FatVolume::open(cursor);
match result {
Ok(fs) => {
use hadris_fat::FatType;
assert!(matches!(fs.fat_type(), FatType::Fat12 | FatType::Fat16));
}
Err(Error::UnsupportedFatType(_)) => {
panic!("FAT12/16 should now be supported");
}
Err(_) => {
}
}
}
#[cfg(test)]
mod file_tests {
use hadris_fat::file::ShortFileName;
#[test]
fn test_short_filename_valid() {
let name = *b"TEST TXT";
let result = ShortFileName::new(name);
assert!(result.is_ok());
let sfn = result.unwrap();
assert!(sfn.as_str().starts_with("TEST"));
}
#[test]
fn test_short_filename_with_spaces() {
let name = *b"FILE BIN";
let result = ShortFileName::new(name);
assert!(result.is_ok());
}
#[test]
fn test_short_filename_invalid_lowercase() {
let name = *b"test txt";
let result = ShortFileName::new(name);
let _ = result;
}
#[test]
fn test_short_filename_special_chars() {
let result = ShortFileName::new([
b'$', b'%', b'\'', b'-', b'_', b'@', b'~', b' ', b' ', b' ', b' ',
]);
assert!(result.is_ok());
}
}
#[cfg(feature = "lfn")]
#[cfg(test)]
mod lfn_tests {
use hadris_fat::file::{LfnBuilder, LongFileName};
#[test]
fn test_lfn_empty() {
let lfn = LongFileName::new();
assert!(lfn.is_empty());
assert_eq!(lfn.to_string(), "");
}
#[test]
fn test_lfn_builder_start() {
let mut builder = LfnBuilder::new();
builder.start(0x41, 0x12);
assert!(builder.building);
}
#[test]
fn test_lfn_prepend_ascii() {
let mut lfn = LongFileName::new();
let name1: [u8; 10] = [
b't', 0, b'e', 0, b's', 0, b't', 0, 0x00, 0x00, ];
let name2: [u8; 12] = [0xFF; 12]; let name3: [u8; 4] = [0xFF; 4];
lfn.prepend_lfn_entry(&name1, &name2, &name3);
assert_eq!(lfn.to_string(), "test");
}
#[test]
fn test_lfn_prepend_multiple() {
let mut lfn = LongFileName::new();
let name1_2: [u8; 10] = [b'f', 0, b'i', 0, b'l', 0, b'e', 0, b'.', 0];
let name2_2: [u8; 12] = [
b't', 0, b'x', 0, b't', 0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
];
let name3_2: [u8; 4] = [0xFF; 4];
lfn.prepend_lfn_entry(&name1_2, &name2_2, &name3_2);
let name1_1: [u8; 10] = [b'l', 0, b'o', 0, b'n', 0, b'g', 0, b'_', 0];
let name2_1: [u8; 12] = [0xFF; 12];
let name3_1: [u8; 4] = [0xFF; 4];
lfn.prepend_lfn_entry(&name1_1, &name2_1, &name3_1);
assert_eq!(lfn.to_string(), "long_file.txt");
}
}
#[cfg(feature = "write")]
mod integration_tests {
use hadris_fat::format::{FatFormatOptions, FatVolumeFormatter};
use hadris_fat::{FatVolume, FatVolumeWriteExt};
use std::io::Cursor;
pub fn create_test_fat32_image() -> Cursor<Vec<u8>> {
let volume_size: u64 = 4 * 1024 * 1024;
let buffer = vec![0u8; volume_size as usize];
let mut cursor = Cursor::new(buffer);
let opts = FatFormatOptions::new(volume_size);
let fs =
FatVolumeFormatter::format(&mut cursor, opts).expect("Failed to format FAT32 volume");
let root = fs.root_dir();
let hello_entry = fs.create_file(&root, "HELLO.TXT").unwrap();
let mut hello_writer = fs.write_file(&hello_entry).unwrap();
hello_writer.write(b"Hello, World!").unwrap();
hello_writer.finish().unwrap();
let data_entry = fs.create_file(&root, "DATA.BIN").unwrap();
let mut data_writer = fs.write_file(&data_entry).unwrap();
let data_content = vec![0xAA; 1024];
data_writer.write(&data_content).unwrap();
data_writer.finish().unwrap();
let subdir = fs.create_dir(&root, "SUBDIR").unwrap();
let nested_entry = fs.create_file(&subdir, "NESTED.TXT").unwrap();
let mut nested_writer = fs.write_file(&nested_entry).unwrap();
nested_writer.write(b"Nested file content").unwrap();
nested_writer.finish().unwrap();
let deep_dir = fs.create_dir(&subdir, "DEEP").unwrap();
let file_entry = fs.create_file(&deep_dir, "FILE.TXT").unwrap();
let mut file_writer = fs.write_file(&file_entry).unwrap();
file_writer.write(b"Deep file").unwrap();
file_writer.finish().unwrap();
fs.sync().unwrap();
cursor
}
#[test]
fn test_root_listing_skips_volume_label_entry() {
use hadris_fat::format::{FatFormatOptions, FatTypeSelection, FatVolumeFormatter};
let volume_size: u64 = 256 * 1024 * 1024;
let buffer = vec![0u8; volume_size as usize];
let mut cursor = Cursor::new(buffer);
let opts = FatFormatOptions::new(volume_size)
.volume_label("PMOS_BOOT")
.fat_type(FatTypeSelection::Fat32);
let fs = FatVolumeFormatter::format(&mut cursor, opts).expect("format FAT32");
let entries: Vec<_> = fs.root_dir().entries().filter_map(|e| e.ok()).collect();
assert!(
entries.is_empty(),
"volume label must not appear in directory listing"
);
assert_eq!(
fs.read_root_label().expect("read_root_label"),
Some(*b"PMOS_BOOT ")
);
}
#[test]
fn test_root_listing_skips_mkfs_style_lowercase_label() {
use hadris_fat::format::{FatFormatOptions, FatTypeSelection, FatVolumeFormatter};
use hadris_io::Seek;
let volume_size: u64 = 256 * 1024 * 1024;
let mut buffer = vec![0u8; volume_size as usize];
let mut cursor = Cursor::new(&mut buffer);
let opts = FatFormatOptions::new(volume_size)
.volume_label("PLACEHOLDER")
.fat_type(FatTypeSelection::Fat32);
{
let fs = FatVolumeFormatter::format(&mut cursor, opts).expect("format FAT32");
fs.set_root_label(b"pmOS_boot ").expect("set_root_label");
}
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("re-open FAT32");
let entries: Vec<_> = fs.root_dir().entries().filter_map(|e| e.ok()).collect();
assert!(
entries.is_empty(),
"lowercase mkfs.fat-style volume label must not break listing"
);
assert_eq!(
fs.read_root_label().expect("read_root_label"),
Some(*b"pmOS_boot ")
);
}
#[test]
fn test_read_directory_entries() {
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let root = fs.root_dir();
let entries: Vec<_> = root
.entries()
.filter_map(|e| e.ok())
.filter(|e| {
let name = e.name();
name != "." && name != ".."
})
.collect();
assert_eq!(entries.len(), 3, "Expected 3 entries in root directory");
let names: Vec<_> = entries.iter().map(|e| e.name()).collect();
assert!(
names.iter().any(|n| n.starts_with("HELLO")),
"Should find HELLO.TXT"
);
assert!(
names.iter().any(|n| n.starts_with("DATA")),
"Should find DATA.BIN"
);
assert!(
names.iter().any(|n| n.starts_with("SUBDIR")),
"Should find SUBDIR"
);
}
#[test]
fn test_read_file_contents() {
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let root = fs.root_dir();
let mut hello_reader = root.open_file("HELLO.TXT").unwrap();
let hello_content = hello_reader.read_to_vec().unwrap();
assert_eq!(String::from_utf8(hello_content).unwrap(), "Hello, World!");
let mut data_reader = root.open_file("DATA.BIN").unwrap();
let data_content = data_reader.read_to_vec().unwrap();
assert_eq!(data_content.len(), 1024);
assert!(data_content.iter().all(|&b| b == 0xAA));
}
#[test]
#[cfg(feature = "lfn")]
fn test_read_lfn_entries() {
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let root = fs.root_dir();
let result = root.find("HELLO.TXT");
assert!(result.is_ok());
assert!(result.unwrap().is_some(), "Should find HELLO.TXT");
let entries: Vec<_> = root
.entries()
.filter_map(|e| e.ok())
.filter(|e| {
let name = e.name();
name != "." && name != ".."
})
.collect();
assert_eq!(entries.len(), 3, "Expected 3 entries in root directory");
}
}
#[cfg(test)]
#[cfg(feature = "write")]
mod navigation_tests {
use super::integration_tests::create_test_fat32_image;
use hadris_fat::{Error, FatVolume};
use hadris_io::Seek;
#[test]
fn test_find_nonexistent_returns_none() {
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let root = fs.root_dir();
let result = root.find("NONEXISTENT.TXT");
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_open_dir_on_file_returns_error() {
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let root = fs.root_dir();
let result = root.open_dir("HELLO.TXT");
assert!(result.is_err());
match result {
Err(Error::NotADirectory) => {}
_ => panic!("Expected NotADirectory error"),
}
}
#[test]
fn test_open_file_on_directory_returns_error() {
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let root = fs.root_dir();
let result = root.open_file("SUBDIR");
assert!(result.is_err());
match result {
Err(Error::NotAFile) => {}
_ => panic!("Expected NotAFile error"),
}
}
#[test]
fn test_error_display() {
let err = Error::EntryNotFound;
assert_eq!(format!("{err}"), "entry not found in directory");
let err = Error::InvalidPath;
assert_eq!(format!("{err}"), "path is invalid (empty or malformed)");
}
#[cfg(feature = "alloc")]
mod path_tests {
use hadris_fat::Error;
#[test]
fn test_invalid_path_empty() {
let _: Result<(), Error> = Err(Error::InvalidPath);
let _: Result<(), Error> = Err(Error::EntryNotFound);
}
#[test]
#[cfg(feature = "write")]
fn test_open_path_empty_returns_invalid() {
use super::super::integration_tests::create_test_fat32_image;
use hadris_fat::FatVolume;
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let result = fs.open_path("");
assert!(result.is_err());
match result {
Err(Error::InvalidPath) => {}
_ => panic!("Expected InvalidPath error"),
}
}
#[test]
#[cfg(feature = "write")]
fn test_open_path_slash_only_returns_invalid() {
use super::super::integration_tests::create_test_fat32_image;
use hadris_fat::FatVolume;
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let result = fs.open_path("/");
assert!(result.is_err());
match result {
Err(Error::InvalidPath) => {}
_ => panic!("Expected InvalidPath error"),
}
}
#[test]
#[cfg(feature = "write")]
fn test_open_path_traversal() {
use super::super::integration_tests::create_test_fat32_image;
use hadris_fat::FatVolume;
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let result = fs.open_path("SUBDIR/DEEP/FILE.TXT");
assert!(result.is_ok(), "Path traversal should work");
let entry = result.unwrap();
assert!(entry.is_file());
assert_eq!(entry.len(), 9); }
#[test]
#[cfg(feature = "write")]
fn test_open_file_path() {
use super::super::integration_tests::create_test_fat32_image;
use hadris_fat::FatVolume;
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let mut reader = fs.open_file_path("SUBDIR/NESTED.TXT").unwrap();
let content = reader.read_to_vec().unwrap();
assert_eq!(String::from_utf8(content).unwrap(), "Nested file content");
}
#[test]
#[cfg(feature = "write")]
fn test_open_dir_path() {
use super::super::integration_tests::create_test_fat32_image;
use hadris_fat::FatVolume;
use hadris_io::Seek;
let mut cursor = create_test_fat32_image();
cursor.seek(std::io::SeekFrom::Start(0).into()).unwrap();
let fs = FatVolume::open(cursor).expect("Failed to open FAT32 image");
let dir = fs.open_dir_path("SUBDIR/DEEP").unwrap();
let entries: Vec<_> = dir
.entries()
.filter_map(|e| e.ok())
.filter(|e| {
let name = e.name();
name != "." && name != ".."
})
.collect();
assert_eq!(entries.len(), 1);
assert!(entries[0].name().starts_with("FILE"));
}
}
}