use core::future::Future;
use ekv::flash::Flash;
use ekv::{Config, Database};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use futures::executor::block_on;
use crate::{EkvFs, Error};
struct MemFlash {
data: alloc::vec::Vec<u8>,
}
impl MemFlash {
fn new(size: usize) -> Self {
Self {
data: alloc::vec![0xFF; size],
}
}
}
impl Flash for MemFlash {
type Error = core::convert::Infallible;
fn page_count(&self) -> usize {
self.data.len() / 4096
}
async fn erase(&mut self, page_id: ekv::flash::PageID) -> Result<(), Self::Error> {
let address = page_id.index() * 4096;
for b in self.data[address..address + 4096].iter_mut() {
*b = 0xFF;
}
Ok(())
}
async fn read(
&mut self,
page_id: ekv::flash::PageID,
offset: usize,
bytes: &mut [u8],
) -> Result<(), Self::Error> {
let address = page_id.index() * 4096 + offset;
bytes.copy_from_slice(&self.data[address..address + bytes.len()]);
Ok(())
}
async fn write(
&mut self,
page_id: ekv::flash::PageID,
offset: usize,
bytes: &[u8],
) -> Result<(), Self::Error> {
let address = page_id.index() * 4096 + offset;
self.data[address..address + bytes.len()].copy_from_slice(bytes);
Ok(())
}
}
fn run<F, Fut>(f: F)
where
F: FnOnce() -> Fut,
Fut: Future<Output = ()>,
{
block_on(f());
}
#[test]
fn lifecycle() {
run(|| async {
let mut flash = MemFlash::new(128 * 1024);
let db = Database::<_, NoopRawMutex>::new(&mut flash, Config::default());
db.format().await.unwrap();
let fs = EkvFs::<_, NoopRawMutex, 64, 512>::new(&db);
let path = "/test/hola.txt";
let payload = b"Hello, Vextos!";
fs.write_file(path, payload).await.unwrap();
let meta = fs.stat(path).await.unwrap();
assert_eq!(meta.size, payload.len());
assert_eq!(meta.chunks, 1);
let mut file = fs.open(path).await.unwrap();
let mut buf = [0u8; 32];
let n = file.read(&mut buf).await.unwrap();
assert_eq!(n, payload.len());
assert_eq!(&buf[..n], payload);
fs.delete_file(path).await.unwrap();
assert_eq!(fs.stat(path).await, Err(Error::NotFound));
});
}
#[test]
fn chunking_and_incremental_read() {
run(|| async {
let mut flash = MemFlash::new(128 * 1024);
let db = Database::<_, NoopRawMutex>::new(&mut flash, Config::default());
db.format().await.unwrap();
let fs = EkvFs::<_, NoopRawMutex, 64, 16>::new(&db);
let path = "/test/chunks.bin";
let payload = [0xAA; 40];
fs.write_file(path, &payload).await.unwrap();
let meta = fs.stat(path).await.unwrap();
assert_eq!(meta.size, 40);
assert_eq!(meta.chunks, 3);
let mut file = fs.open(path).await.unwrap();
let mut read_buf = [0u8; 10];
let mut total = 0usize;
loop {
let n = file.read(&mut read_buf).await.unwrap();
if n == 0 {
break;
}
assert_eq!(&read_buf[..n], &payload[total..total + n]);
total += n;
}
assert_eq!(total, payload.len());
});
}
#[test]
fn overwrite_replaces_content_and_metadata() {
run(|| async {
let mut flash = MemFlash::new(128 * 1024);
let db = Database::<_, NoopRawMutex>::new(&mut flash, Config::default());
db.format().await.unwrap();
let fs = EkvFs::<_, NoopRawMutex, 64, 16>::new(&db);
let path = "/test/overwrite.bin";
let large = [0xBB; 40];
let small = [0xCC; 8];
fs.write_file(path, &large).await.unwrap();
assert_eq!(fs.stat(path).await.unwrap().chunks, 3);
fs.write_file(path, &small).await.unwrap();
let meta = fs.stat(path).await.unwrap();
assert_eq!(meta.size, 8);
assert_eq!(meta.chunks, 1);
let mut file = fs.open(path).await.unwrap();
let mut buf = [0u8; 16];
let n = file.read(&mut buf).await.unwrap();
assert_eq!(n, 8);
assert_eq!(&buf[..n], &small);
});
}
#[test]
fn empty_file() {
run(|| async {
let mut flash = MemFlash::new(128 * 1024);
let db = Database::<_, NoopRawMutex>::new(&mut flash, Config::default());
db.format().await.unwrap();
let fs = EkvFs::<_, NoopRawMutex, 64, 512>::new(&db);
let path = "/empty.dat";
fs.write_file(path, &[]).await.unwrap();
let meta = fs.stat(path).await.unwrap();
assert_eq!(meta.size, 0);
assert_eq!(meta.chunks, 0);
let mut file = fs.open(path).await.unwrap();
let mut buf = [0u8; 4];
assert_eq!(file.read(&mut buf).await.unwrap(), 0);
});
}
#[test]
fn path_too_long() {
run(|| async {
let mut flash = MemFlash::new(128 * 1024);
let db = Database::<_, NoopRawMutex>::new(&mut flash, Config::default());
db.format().await.unwrap();
let fs = EkvFs::<_, NoopRawMutex, 64, 512>::new(&db);
let path = "/this/path/is/definitely/longer/than/sixty/four/bytes/allowed.txt";
assert_eq!(fs.stat(path).await, Err(Error::PathTooLong));
assert_eq!(fs.write_file(path, b"x").await, Err(Error::PathTooLong));
});
}
#[test]
fn not_found() {
run(|| async {
let mut flash = MemFlash::new(128 * 1024);
let db = Database::<_, NoopRawMutex>::new(&mut flash, Config::default());
db.format().await.unwrap();
let fs = EkvFs::<_, NoopRawMutex, 64, 512>::new(&db);
let path = "/missing.bin";
assert_eq!(fs.stat(path).await, Err(Error::NotFound));
assert!(matches!(fs.open(path).await, Err(Error::NotFound)));
assert_eq!(fs.delete_file(path).await, Err(Error::NotFound));
});
}