use embedded_sdmmc::{LfnBuffer, Mode, ShortFileName};
mod utils;
#[derive(Debug, Clone)]
struct ExpectedDirEntry {
name: String,
mtime: String,
ctime: String,
size: u32,
is_dir: bool,
}
impl PartialEq<embedded_sdmmc::DirEntry> for ExpectedDirEntry {
fn eq(&self, other: &embedded_sdmmc::DirEntry) -> bool {
if other.name.to_string() != self.name {
return false;
}
if format!("{}", other.mtime) != self.mtime {
return false;
}
if format!("{}", other.ctime) != self.ctime {
return false;
}
if other.size != self.size {
return false;
}
if other.attributes.is_directory() != self.is_dir {
return false;
}
true
}
}
#[test]
fn fat16_root_directory_listing() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source);
let fat16_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(0))
.expect("open volume 0");
let root_dir = volume_mgr
.open_root_dir(fat16_volume)
.expect("open root dir");
let expected = [
(
ExpectedDirEntry {
name: String::from("README.TXT"),
mtime: String::from("2018-12-09 19:22:34"),
ctime: String::from("2018-12-09 19:22:34"),
size: 258,
is_dir: false,
},
None,
),
(
ExpectedDirEntry {
name: String::from("EMPTY.DAT"),
mtime: String::from("2018-12-09 19:21:16"),
ctime: String::from("2018-12-09 19:21:16"),
size: 0,
is_dir: false,
},
None,
),
(
ExpectedDirEntry {
name: String::from("TEST"),
mtime: String::from("2018-12-09 19:23:16"),
ctime: String::from("2018-12-09 19:23:16"),
size: 0,
is_dir: true,
},
None,
),
(
ExpectedDirEntry {
name: String::from("64MB.DAT"),
mtime: String::from("2018-12-09 19:21:38"),
ctime: String::from("2018-12-09 19:21:38"),
size: 64 * 1024 * 1024,
is_dir: false,
},
None,
),
(
ExpectedDirEntry {
name: String::from("FSEVEN~4"),
mtime: String::from("2024-10-25 16:30:42"),
ctime: String::from("2024-10-25 16:30:42"),
size: 0,
is_dir: true,
},
Some(String::from(".fseventsd")),
),
(
ExpectedDirEntry {
name: String::from("P-FAT16"),
mtime: String::from("2024-10-30 18:43:12"),
ctime: String::from("2024-10-30 18:43:12"),
size: 0,
is_dir: false,
},
None,
),
];
let mut listing = Vec::new();
let mut storage = [0u8; 128];
let mut lfn_buffer: LfnBuffer = LfnBuffer::new(&mut storage);
volume_mgr
.iterate_dir_lfn(root_dir, &mut lfn_buffer, |d, opt_lfn| {
listing.push((d.clone(), opt_lfn.map(String::from)));
})
.expect("iterate directory");
for (expected_entry, given_entry) in expected.iter().zip(listing.iter()) {
assert_eq!(
expected_entry.0, given_entry.0,
"{:#?} does not match {:#?}",
given_entry, expected_entry
);
assert_eq!(
expected_entry.1, given_entry.1,
"{:#?} does not match {:#?}",
given_entry, expected_entry
);
}
assert_eq!(
expected.len(),
listing.len(),
"{:#?} != {:#?}",
expected,
listing
);
}
#[test]
fn fat16_sub_directory_listing() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source);
let fat16_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(0))
.expect("open volume 0");
let root_dir = volume_mgr
.open_root_dir(fat16_volume)
.expect("open root dir");
let test_dir = volume_mgr
.open_dir(root_dir, "TEST")
.expect("open test dir");
let expected = [
ExpectedDirEntry {
name: String::from("."),
mtime: String::from("2018-12-09 19:21:02"),
ctime: String::from("2018-12-09 19:21:02"),
size: 0,
is_dir: true,
},
ExpectedDirEntry {
name: String::from(".."),
mtime: String::from("2018-12-09 19:21:02"),
ctime: String::from("2018-12-09 19:21:02"),
size: 0,
is_dir: true,
},
ExpectedDirEntry {
name: String::from("TEST.DAT"),
mtime: String::from("2018-12-09 19:22:12"),
ctime: String::from("2018-12-09 19:22:12"),
size: 3500,
is_dir: false,
},
];
let mut listing = Vec::new();
let mut count = 0;
volume_mgr
.iterate_dir(test_dir, |d| {
if count == 0 {
assert!(d.name == ShortFileName::this_dir());
} else if count == 1 {
assert!(d.name == ShortFileName::parent_dir());
}
count += 1;
listing.push(d.clone());
})
.expect("iterate directory");
for (expected_entry, given_entry) in expected.iter().zip(listing.iter()) {
assert_eq!(
expected_entry, given_entry,
"{:#?} does not match {:#?}",
given_entry, expected_entry
);
}
assert_eq!(
expected.len(),
listing.len(),
"{:#?} != {:#?}",
expected,
listing
);
}
#[test]
fn fat32_root_directory_listing() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source);
let fat32_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(1))
.expect("open volume 1");
let root_dir = volume_mgr
.open_root_dir(fat32_volume)
.expect("open root dir");
let expected = [
(
ExpectedDirEntry {
name: String::from("64MB.DAT"),
mtime: String::from("2018-12-09 19:22:56"),
ctime: String::from("2018-12-09 19:22:56"),
size: 64 * 1024 * 1024,
is_dir: false,
},
None,
),
(
ExpectedDirEntry {
name: String::from("EMPTY.DAT"),
mtime: String::from("2018-12-09 19:22:56"),
ctime: String::from("2018-12-09 19:22:56"),
size: 0,
is_dir: false,
},
None,
),
(
ExpectedDirEntry {
name: String::from("README.TXT"),
mtime: String::from("2023-09-21 09:48:06"),
ctime: String::from("2018-12-09 19:22:56"),
size: 258,
is_dir: false,
},
None,
),
(
ExpectedDirEntry {
name: String::from("TEST"),
mtime: String::from("2018-12-09 19:23:20"),
ctime: String::from("2018-12-09 19:23:20"),
size: 0,
is_dir: true,
},
None,
),
(
ExpectedDirEntry {
name: String::from("FSEVEN~4"),
mtime: String::from("2024-10-25 16:30:42"),
ctime: String::from("2024-10-25 16:30:42"),
size: 0,
is_dir: true,
},
Some(String::from(".fseventsd")),
),
(
ExpectedDirEntry {
name: String::from("P-FAT32"),
mtime: String::from("2024-10-30 18:43:16"),
ctime: String::from("2024-10-30 18:43:16"),
size: 0,
is_dir: false,
},
None,
),
(
ExpectedDirEntry {
name: String::from("THISIS~9"),
mtime: String::from("2024-10-25 16:30:54"),
ctime: String::from("2024-10-25 16:30:50"),
size: 0,
is_dir: true,
},
Some(String::from("This is a long file name £99")),
),
(
ExpectedDirEntry {
name: String::from("COPYO~13.TXT"),
mtime: String::from("2024-10-25 16:31:14"),
ctime: String::from("2018-12-09 19:22:56"),
size: 258,
is_dir: false,
},
Some(String::from("Copy of Readme.txt")),
),
];
let mut listing = Vec::new();
let mut storage = [0u8; 128];
let mut lfn_buffer: LfnBuffer = LfnBuffer::new(&mut storage);
volume_mgr
.iterate_dir_lfn(root_dir, &mut lfn_buffer, |d, opt_lfn| {
listing.push((d.clone(), opt_lfn.map(String::from)));
})
.expect("iterate directory");
for (expected_entry, given_entry) in expected.iter().zip(listing.iter()) {
assert_eq!(
expected_entry.0, given_entry.0,
"{:#?} does not match {:#?}",
given_entry, expected_entry
);
assert_eq!(
expected_entry.1, given_entry.1,
"{:#?} does not match {:#?}",
given_entry, expected_entry
);
}
assert_eq!(
expected.len(),
listing.len(),
"{:#?} != {:#?}",
expected,
listing
);
}
#[test]
fn open_dir_twice() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source);
let fat32_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(1))
.expect("open volume 1");
let root_dir = volume_mgr
.open_root_dir(fat32_volume)
.expect("open root dir");
let root_dir2 = volume_mgr
.open_root_dir(fat32_volume)
.expect("open it again");
assert!(matches!(
volume_mgr.open_dir(root_dir, "README.TXT"),
Err(embedded_sdmmc::Error::OpenedFileAsDir)
));
let test_dir = volume_mgr
.open_dir(root_dir, "TEST")
.expect("open test dir");
let test_dir2 = volume_mgr.open_dir(root_dir, "TEST").unwrap();
volume_mgr.close_dir(root_dir).expect("close root dir");
volume_mgr.close_dir(test_dir).expect("close test dir");
volume_mgr.close_dir(test_dir2).expect("close test dir");
volume_mgr.close_dir(root_dir2).expect("close test dir");
assert!(matches!(
volume_mgr.close_dir(test_dir),
Err(embedded_sdmmc::Error::BadHandle)
));
}
#[test]
fn open_too_many_dirs() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr: embedded_sdmmc::VolumeManager<
utils::RamDisk<Vec<u8>>,
utils::TestTimeSource,
1,
4,
2,
> = embedded_sdmmc::VolumeManager::new_with_limits(disk, time_source, 0x1000_0000);
let fat32_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(1))
.expect("open volume 1");
let root_dir = volume_mgr
.open_root_dir(fat32_volume)
.expect("open root dir");
assert!(matches!(
volume_mgr.open_dir(root_dir, "TEST"),
Err(embedded_sdmmc::Error::TooManyOpenDirs)
));
}
#[test]
fn find_dir_entry() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source);
let fat32_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(1))
.expect("open volume 1");
let root_dir = volume_mgr
.open_root_dir(fat32_volume)
.expect("open root dir");
let dir_entry = volume_mgr
.find_directory_entry(root_dir, "README.TXT")
.expect("Find directory entry");
assert!(dir_entry.attributes.is_archive());
assert!(!dir_entry.attributes.is_directory());
assert!(!dir_entry.attributes.is_hidden());
assert!(!dir_entry.attributes.is_lfn());
assert!(!dir_entry.attributes.is_system());
assert!(!dir_entry.attributes.is_volume());
assert!(matches!(
volume_mgr.find_directory_entry(root_dir, "README.TXS"),
Err(embedded_sdmmc::Error::NotFound)
));
}
#[test]
fn delete_file() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source);
let fat32_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(1))
.expect("open volume 1");
let root_dir = volume_mgr
.open_root_dir(fat32_volume)
.expect("open root dir");
let file = volume_mgr
.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly)
.unwrap();
assert!(matches!(
volume_mgr.delete_file_in_dir(root_dir, "README.TXT"),
Err(embedded_sdmmc::Error::FileAlreadyOpen)
));
assert!(matches!(
volume_mgr.delete_file_in_dir(root_dir, "README2.TXT"),
Err(embedded_sdmmc::Error::NotFound)
));
volume_mgr.close_file(file).unwrap();
volume_mgr
.delete_file_in_dir(root_dir, "README.TXT")
.unwrap();
assert!(matches!(
volume_mgr.delete_file_in_dir(root_dir, "README.TXT"),
Err(embedded_sdmmc::Error::NotFound)
));
assert!(matches!(
volume_mgr.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly),
Err(embedded_sdmmc::Error::NotFound)
));
}
#[test]
fn make_directory() {
let time_source = utils::make_time_source();
let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap();
let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source);
let fat32_volume = volume_mgr
.open_raw_volume(embedded_sdmmc::VolumeIdx(1))
.expect("open volume 1");
let root_dir = volume_mgr
.open_root_dir(fat32_volume)
.expect("open root dir");
let test_dir_name = ShortFileName::create_from_str("12345678.ABC").unwrap();
let test_file_name = ShortFileName::create_from_str("ABC.TXT").unwrap();
volume_mgr
.make_dir_in_dir(root_dir, &test_dir_name)
.unwrap();
let new_dir = volume_mgr.open_dir(root_dir, &test_dir_name).unwrap();
let mut has_this = false;
let mut has_parent = false;
volume_mgr
.iterate_dir(new_dir, |item| {
if item.name == ShortFileName::parent_dir() {
has_parent = true;
assert!(item.attributes.is_directory());
assert_eq!(item.size, 0);
assert_eq!(item.mtime.to_string(), utils::get_time_source_string());
assert_eq!(item.ctime.to_string(), utils::get_time_source_string());
} else if item.name == ShortFileName::this_dir() {
has_this = true;
assert!(item.attributes.is_directory());
assert_eq!(item.size, 0);
assert_eq!(item.mtime.to_string(), utils::get_time_source_string());
assert_eq!(item.ctime.to_string(), utils::get_time_source_string());
} else {
panic!("Unexpected item in new dir");
}
})
.unwrap();
assert!(has_this);
assert!(has_parent);
let new_file = volume_mgr
.open_file_in_dir(
new_dir,
&test_file_name,
embedded_sdmmc::Mode::ReadWriteCreate,
)
.expect("open new file");
volume_mgr
.write(new_file, b"Hello")
.expect("write to new file");
volume_mgr.close_file(new_file).expect("close new file");
let mut has_this = false;
let mut has_parent = false;
let mut has_new_file = false;
volume_mgr
.iterate_dir(new_dir, |item| {
if item.name == ShortFileName::parent_dir() {
has_parent = true;
assert!(item.attributes.is_directory());
assert_eq!(item.size, 0);
assert_eq!(item.mtime.to_string(), utils::get_time_source_string());
assert_eq!(item.ctime.to_string(), utils::get_time_source_string());
} else if item.name == ShortFileName::this_dir() {
has_this = true;
assert!(item.attributes.is_directory());
assert_eq!(item.size, 0);
assert_eq!(item.mtime.to_string(), utils::get_time_source_string());
assert_eq!(item.ctime.to_string(), utils::get_time_source_string());
} else if item.name == test_file_name {
has_new_file = true;
assert_eq!(item.size, 5);
assert!(!item.attributes.is_directory());
assert_eq!(item.mtime.to_string(), utils::get_time_source_string());
assert_eq!(item.ctime.to_string(), utils::get_time_source_string());
} else {
panic!("Unexpected item in new dir");
}
})
.unwrap();
assert!(has_this);
assert!(has_parent);
assert!(has_new_file);
volume_mgr.close_dir(root_dir).expect("close root");
volume_mgr.close_dir(new_dir).expect("close new_dir");
let root_dir = volume_mgr
.open_root_dir(fat32_volume)
.expect("open root dir");
assert!(volume_mgr
.make_dir_in_dir(root_dir, &test_dir_name)
.is_err());
let new_dir = volume_mgr
.open_dir(root_dir, &test_dir_name)
.expect("find new dir");
let new_file = volume_mgr
.open_file_in_dir(new_dir, &test_file_name, embedded_sdmmc::Mode::ReadOnly)
.expect("re-open new file");
volume_mgr.close_dir(root_dir).expect("close root");
volume_mgr.close_dir(new_dir).expect("close new dir");
volume_mgr.close_file(new_file).expect("close file");
}