use std::cell::Cell;
use rsext4::{
bmalloc::AbsoluteBN,
error::{Ext4Error, Ext4Result},
*,
};
struct MockBlockDevice {
data: Vec<u8>,
block_size: u32,
now: Cell<i64>,
}
impl MockBlockDevice {
fn new(size: usize) -> Self {
Self {
data: vec![0; size],
block_size: rsext4::BLOCK_SIZE as u32,
now: Cell::new(1_700_000_000),
}
}
}
impl BlockDevice for MockBlockDevice {
fn read(&mut self, buffer: &mut [u8], block_id: AbsoluteBN, _count: u32) -> Ext4Result<()> {
let start = block_id.as_usize()? * self.block_size as usize;
let end = start + buffer.len();
if end > self.data.len() {
return Err(Ext4Error::block_out_of_range(
block_id.to_u32()?,
(self.data.len() / self.block_size as usize) as u64,
));
}
buffer.copy_from_slice(&self.data[start..end]);
Ok(())
}
fn write(&mut self, buffer: &[u8], block_id: AbsoluteBN, _count: u32) -> Ext4Result<()> {
let start = block_id.as_usize()? * self.block_size as usize;
let end = start + buffer.len();
if end > self.data.len() {
return Err(Ext4Error::block_out_of_range(
block_id.to_u32()?,
(self.data.len() / self.block_size as usize) as u64,
));
}
self.data[start..end].copy_from_slice(buffer);
Ok(())
}
fn open(&mut self) -> Ext4Result<()> {
Ok(())
}
fn close(&mut self) -> Ext4Result<()> {
Ok(())
}
fn total_blocks(&self) -> u64 {
(self.data.len() / self.block_size as usize) as u64
}
fn block_size(&self) -> u32 {
self.block_size
}
fn current_time(&self) -> Ext4Result<Ext4Timestamp> {
let sec = self.now.get();
self.now.set(sec + 1);
Ok(Ext4Timestamp::new(sec, 0))
}
}
#[cfg(test)]
mod api_functional_tests {
use super::*;
#[test]
fn test_open_and_read() {
let device = MockBlockDevice::new(100 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
let test_data = b"API test data for basic operations";
mkfile(
&mut jbd2_dev,
&mut fs,
"/apitest/data.txt",
Some(test_data),
None,
)
.expect("mkfile failed");
let mut file =
open(&mut jbd2_dev, &mut fs, "/apitest/data.txt", false).expect("open failed");
let read_data =
read_at(&mut jbd2_dev, &mut fs, &mut file, test_data.len()).expect("read_at failed");
assert_eq!(read_data, test_data);
umount(fs, &mut jbd2_dev).expect("umount failed");
}
#[test]
fn test_write_at() {
let device = MockBlockDevice::new(100 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
mkdir(&mut jbd2_dev, &mut fs, "/write_test").expect("mkdir failed");
mkfile(&mut jbd2_dev, &mut fs, "/write_test/empty.txt", None, None).expect("mkfile failed");
let mut file =
open(&mut jbd2_dev, &mut fs, "/write_test/empty.txt", true).expect("open failed");
let write_data = b"This is test data for write_at function";
write_at(&mut jbd2_dev, &mut fs, &mut file, write_data).expect("write_at failed");
lseek(&mut file, 0).expect("lseek failed");
let read_data =
read_at(&mut jbd2_dev, &mut fs, &mut file, write_data.len()).expect("read_at failed");
assert_eq!(read_data, write_data);
umount(fs, &mut jbd2_dev).expect("umount failed");
}
#[test]
fn test_lseek() {
let device = MockBlockDevice::new(100 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
let test_data = b"0123456789ABCDEFGHIJ";
mkfile(
&mut jbd2_dev,
&mut fs,
"/seek_test.txt",
Some(test_data),
None,
)
.expect("mkfile failed");
let mut file = open(&mut jbd2_dev, &mut fs, "/seek_test.txt", false).expect("open failed");
lseek(&mut file, 0).expect("lseek failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, 5).expect("read_at failed");
assert_eq!(data, b"01234");
lseek(&mut file, 10).expect("lseek failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, 5).expect("read_at failed");
assert_eq!(data, b"ABCDE");
lseek(&mut file, test_data.len() as u64).expect("lseek failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, 1).expect("read_at failed");
assert_eq!(data, b"");
umount(fs, &mut jbd2_dev).expect("umount failed");
}
#[test]
fn test_random_read_write() {
let device = MockBlockDevice::new(100 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
let mut initial_data = Vec::new();
for i in 0..1000 {
initial_data.push((i % 256) as u8);
}
mkfile(
&mut jbd2_dev,
&mut fs,
"/random_test.dat",
Some(&initial_data),
None,
)
.expect("mkfile failed");
let mut file = open(&mut jbd2_dev, &mut fs, "/random_test.dat", true).expect("open failed");
let write_positions = [100, 250, 500, 750];
let write_data = b"DATA";
for &pos in &write_positions {
lseek(&mut file, pos).expect("lseek failed");
write_at(&mut jbd2_dev, &mut fs, &mut file, write_data).expect("write_at failed");
}
for &pos in &write_positions {
lseek(&mut file, pos).expect("lseek failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, write_data.len())
.expect("read_at failed");
assert_eq!(data, write_data);
}
umount(fs, &mut jbd2_dev).expect("umount failed");
}
#[test]
fn test_large_file_operations() {
let device = MockBlockDevice::new(200 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
let chunk = b"0123456789ABCDEF";
let chunks_to_write = 1000; let expected_size = chunk.len() * chunks_to_write;
mkfile(&mut jbd2_dev, &mut fs, "/large_file.dat", None, None).expect("mkfile failed");
let mut file = open(&mut jbd2_dev, &mut fs, "/large_file.dat", true).expect("open failed");
for _ in 0..chunks_to_write {
write_at(&mut jbd2_dev, &mut fs, &mut file, chunk).expect("write_at failed");
}
lseek(&mut file, 0).expect("lseek failed");
let data =
read_at(&mut jbd2_dev, &mut fs, &mut file, expected_size).expect("read_at failed");
assert_eq!(data.len(), expected_size);
for i in 0..chunks_to_write {
let start = i * chunk.len();
let end = start + chunk.len();
assert_eq!(&data[start..end], chunk);
}
umount(fs, &mut jbd2_dev).expect("umount failed");
}
#[test]
fn test_concurrent_file_operations() {
let device = MockBlockDevice::new(200 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
for i in 1..=5 {
let filename = format!("/concurrent/file{}.txt", i);
let data = format!("Content of file {}", i);
mkfile(
&mut jbd2_dev,
&mut fs,
&filename,
Some(data.as_bytes()),
None,
)
.expect("mkfile failed");
}
for i in 1..=5 {
let filename = format!("/concurrent/file{}.txt", i);
let mut file = open(&mut jbd2_dev, &mut fs, &filename, false).expect("open failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, 10).expect("read_at failed");
assert_eq!(data, format!("Content of").as_bytes());
drop(file);
}
umount(fs, &mut jbd2_dev).expect("umount failed");
}
#[test]
fn test_api_error_handling() {
let device = MockBlockDevice::new(100 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
let result = open(&mut jbd2_dev, &mut fs, "/nonexistent.txt", false);
assert!(result.is_err());
let mut file = open(&mut jbd2_dev, &mut fs, "/new.txt", true).expect("open failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, 10).expect("read_at failed");
assert_eq!(data, b"");
let seek_result = lseek(&mut file, u64::MAX);
println!("lseek(u64::MAX) result: {:?}", seek_result);
lseek(&mut file, 0).expect("lseek failed");
let large_data = vec![b'X'; 1024 * 1024]; write_at(&mut jbd2_dev, &mut fs, &mut file, &large_data).expect("write_at failed");
umount(fs, &mut jbd2_dev).expect("umount failed");
}
#[test]
fn test_boundary_conditions() {
let device = MockBlockDevice::new(100 * 1024 * 1024); let mut jbd2_dev = Jbd2Dev::initial_jbd2dev(0, device, true);
mkfs(&mut jbd2_dev).expect("mkfs failed");
let mut fs = mount(&mut jbd2_dev).expect("mount failed");
mkfile(
&mut jbd2_dev,
&mut fs,
"/boundary.txt",
Some(b"Boundary"),
None,
)
.expect("mkfile failed");
let mut file = open(&mut jbd2_dev, &mut fs, "/boundary.txt", true).expect("open failed");
write_at(&mut jbd2_dev, &mut fs, &mut file, b"").expect("write_at failed");
lseek(&mut file, 0).expect("lseek failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, 0).expect("read_at failed");
assert_eq!(data, b"");
lseek(&mut file, 8).expect("lseek failed"); write_at(&mut jbd2_dev, &mut fs, &mut file, b" test").expect("write_at failed");
lseek(&mut file, 8).expect("lseek failed");
let data = read_at(&mut jbd2_dev, &mut fs, &mut file, 5).expect("read_at failed");
assert_eq!(data, b" test");
umount(fs, &mut jbd2_dev).expect("umount failed");
}
}