diff --git a/server/src/engine/storage/v1/mod.rs b/server/src/engine/storage/v1/mod.rs index e8c51f5b..0031839c 100644 --- a/server/src/engine/storage/v1/mod.rs +++ b/server/src/engine/storage/v1/mod.rs @@ -34,6 +34,8 @@ pub mod inf; mod start_stop; // test #[cfg(test)] +pub mod test_util; +#[cfg(test)] mod tests; // re-exports diff --git a/server/src/engine/storage/v1/test_util.rs b/server/src/engine/storage/v1/test_util.rs new file mode 100644 index 00000000..a2df59fe --- /dev/null +++ b/server/src/engine/storage/v1/test_util.rs @@ -0,0 +1,220 @@ +/* + * Created on Thu Aug 24 2023 + * + * This file is a part of Skytable + * Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source + * NoSQL database written by Sayan Nandan ("the Author") with the + * vision to provide flexibility in data modelling without compromising + * on performance, queryability or scalability. + * + * Copyright (c) 2023, Sayan Nandan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * +*/ + +use { + super::{ + header_impl::{FileScope, FileSpecifier, FileSpecifierVersion, HostRunMode}, + rw::{FileOpen, RawFileIOInterface, RawFileOpen, SDSSFileIO}, + SDSSResult, + }, + crate::engine::sync::cell::Lazy, + parking_lot::RwLock, + std::{ + collections::hash_map::{Entry, HashMap}, + io::{Error, ErrorKind}, + }, +}; + +static VFS: Lazy, VFile>>, fn() -> RwLock, VFile>>> = + Lazy::new(|| RwLock::new(HashMap::new())); + +#[derive(Debug)] +struct VFile { + read: bool, + write: bool, + data: Vec, + pos: usize, +} + +impl VFile { + fn new(read: bool, write: bool, data: Vec, pos: usize) -> Self { + Self { + read, + write, + data, + pos, + } + } + fn current(&self) -> &[u8] { + &self.data[self.pos..] + } +} + +#[derive(Debug)] +pub struct VirtualFS(Box); + +impl RawFileIOInterface for VirtualFS { + fn fopen_or_create_rw(file_path: &str) -> super::SDSSResult> { + match VFS.write().entry(file_path.into()) { + Entry::Occupied(mut oe) => { + oe.get_mut().read = true; + oe.get_mut().write = true; + oe.get_mut().pos = 0; + Ok(RawFileOpen::Existing(Self(file_path.into()))) + } + Entry::Vacant(v) => { + v.insert(VFile::new(true, true, vec![], 0)); + Ok(RawFileOpen::Created(Self(file_path.into()))) + } + } + } + fn fread_exact(&mut self, buf: &mut [u8]) -> SDSSResult<()> { + let mut vfs = VFS.write(); + let file = vfs + .get_mut(&self.0) + .ok_or(Error::new(ErrorKind::NotFound, "File not found"))?; + + if !file.read { + return Err(Error::new(ErrorKind::PermissionDenied, "Read permission denied").into()); + } + let available_bytes = file.current().len(); + if available_bytes < buf.len() { + return Err(Error::from(ErrorKind::UnexpectedEof).into()); + } + buf.copy_from_slice(&file.data[file.pos..file.pos + buf.len()]); + file.pos += buf.len(); + Ok(()) + } + fn fwrite_all(&mut self, bytes: &[u8]) -> SDSSResult<()> { + let mut vfs = VFS.write(); + let file = vfs + .get_mut(&self.0) + .ok_or(Error::new(ErrorKind::NotFound, "File not found"))?; + + if !file.write { + return Err(Error::new(ErrorKind::PermissionDenied, "Write permission denied").into()); + } + + if file.pos + bytes.len() > file.data.len() { + file.data.resize(file.pos + bytes.len(), 0); + } + file.data[file.pos..file.pos + bytes.len()].copy_from_slice(bytes); + file.pos += bytes.len(); + + Ok(()) + } + fn fsync_all(&mut self) -> super::SDSSResult<()> { + // pretty redundant for us + Ok(()) + } + fn fseek_ahead(&mut self, by: u64) -> SDSSResult<()> { + let mut vfs = VFS.write(); + let file = vfs + .get_mut(&self.0) + .ok_or(Error::new(ErrorKind::NotFound, "File not found"))?; + + if by > file.data.len() as u64 { + return Err(Error::new(ErrorKind::InvalidInput, "Can't seek beyond file's end").into()); + } + + file.pos = by as usize; + Ok(()) + } + + fn flen(&self) -> SDSSResult { + let vfs = VFS.read(); + let file = vfs + .get(&self.0) + .ok_or(Error::new(ErrorKind::NotFound, "File not found"))?; + + Ok(file.data.len() as u64) + } + + fn flen_set(&mut self, to: u64) -> SDSSResult<()> { + let mut vfs = VFS.write(); + let file = vfs + .get_mut(&self.0) + .ok_or(Error::new(ErrorKind::NotFound, "File not found"))?; + + if !file.write { + return Err(Error::new(ErrorKind::PermissionDenied, "Write permission denied").into()); + } + + if to as usize > file.data.len() { + file.data.resize(to as usize, 0); + } else { + file.data.truncate(to as usize); + } + + if file.pos > file.data.len() { + file.pos = file.data.len(); + } + + Ok(()) + } + fn fcursor(&mut self) -> SDSSResult { + let vfs = VFS.read(); + let file = vfs + .get(&self.0) + .ok_or(Error::new(ErrorKind::NotFound, "File not found"))?; + + Ok(file.pos as u64) + } +} + +#[test] +fn sdss_file() { + let f = SDSSFileIO::::open_or_create_perm_rw( + "this_is_a_test_file.db", + FileScope::Journal, + FileSpecifier::TestTransactionLog, + FileSpecifierVersion::__new(0), + 0, + HostRunMode::Prod, + 128, + ) + .unwrap(); + + let FileOpen::Created(mut f) = f else { + panic!() + }; + + f.fsynced_write(b"hello, world\n").unwrap(); + f.fsynced_write(b"hello, again\n").unwrap(); + + let f = SDSSFileIO::::open_or_create_perm_rw( + "this_is_a_test_file.db", + FileScope::Journal, + FileSpecifier::TestTransactionLog, + FileSpecifierVersion::__new(0), + 0, + HostRunMode::Prod, + 128, + ) + .unwrap(); + + let FileOpen::Existing(mut f, _) = f else { + panic!() + }; + + let mut buf1 = [0u8; 13]; + f.read_to_buffer(&mut buf1).unwrap(); + let mut buf2 = [0u8; 13]; + f.read_to_buffer(&mut buf2).unwrap(); + + assert_eq!(&buf1, b"hello, world\n"); + assert_eq!(&buf2, b"hello, again\n"); +} diff --git a/server/src/engine/storage/v1/tests.rs b/server/src/engine/storage/v1/tests.rs index f86b8175..dbe92fc7 100644 --- a/server/src/engine/storage/v1/tests.rs +++ b/server/src/engine/storage/v1/tests.rs @@ -24,144 +24,7 @@ * */ -use { - super::{ - rw::{RawFileIOInterface, RawFileOpen}, - SDSSError, SDSSResult, - }, - crate::engine::sync::cell::Lazy, - parking_lot::RwLock, - std::{ - collections::{hash_map::Entry, HashMap}, - io::{ErrorKind, Read, Write}, - }, -}; - -static VFS: Lazy< - RwLock>, - fn() -> RwLock>, -> = Lazy::new(|| RwLock::new(HashMap::new())); - -fn vfs(fname: &str, mut func: impl FnMut(&mut VirtualFile) -> SDSSResult) -> SDSSResult { - let mut vfs = VFS.write(); - let f = vfs - .get_mut(fname) - .ok_or(SDSSError::from(std::io::Error::from(ErrorKind::NotFound)))?; - func(f) -} - -struct VirtualFile { - pos: u64, - read: bool, - write: bool, - data: Vec, -} - -impl VirtualFile { - fn new(read: bool, write: bool, data: Vec) -> Self { - Self { - read, - write, - data, - pos: 0, - } - } - fn rw(data: Vec) -> Self { - Self::new(true, true, data) - } - fn w(data: Vec) -> Self { - Self::new(false, true, data) - } - fn r(data: Vec) -> Self { - Self::new(true, false, data) - } - fn seek_forward(&mut self, by: u64) { - self.pos += by; - assert!(self.pos <= self.data.len() as u64); - } - fn data(&self) -> &[u8] { - &self.data[self.pos as usize..] - } - fn data_mut(&mut self) -> &mut [u8] { - &mut self.data[self.pos as usize..] - } - fn close(&mut self) { - self.pos = 0; - self.read = false; - self.write = false; - } -} - -struct VirtualFileInterface(Box); - -impl Drop for VirtualFileInterface { - fn drop(&mut self) { - vfs(&self.0, |f| { - f.close(); - Ok(()) - }) - .unwrap(); - } -} - -impl RawFileIOInterface for VirtualFileInterface { - fn fopen_or_create_rw(file_path: &str) -> SDSSResult> { - match VFS.write().entry(file_path.to_owned()) { - Entry::Occupied(mut oe) => { - let file_md = oe.get_mut(); - file_md.read = true; - file_md.write = true; - Ok(RawFileOpen::Existing(Self(file_path.into()))) - } - Entry::Vacant(ve) => { - ve.insert(VirtualFile::rw(vec![])); - Ok(RawFileOpen::Created(Self(file_path.into()))) - } - } - } - fn fread_exact(&mut self, buf: &mut [u8]) -> super::SDSSResult<()> { - vfs(&self.0, |f| { - assert!(f.read); - f.data().read_exact(buf)?; - Ok(()) - }) - } - fn fwrite_all(&mut self, bytes: &[u8]) -> super::SDSSResult<()> { - vfs(&self.0, |f| { - assert!(f.write); - if f.data.len() < bytes.len() { - f.data.extend(bytes); - } else { - f.data_mut().write_all(bytes)?; - } - Ok(()) - }) - } - fn fsync_all(&mut self) -> super::SDSSResult<()> { - Ok(()) - } - fn flen(&self) -> SDSSResult { - vfs(&self.0, |f| Ok(f.data.len() as _)) - } - fn fseek_ahead(&mut self, by: u64) -> SDSSResult<()> { - vfs(&self.0, |f| { - f.seek_forward(by); - Ok(()) - }) - } - fn flen_set(&mut self, to: u64) -> SDSSResult<()> { - vfs(&self.0, |f| { - f.data.drain(f.data.len() - to as usize..); - Ok(()) - }) - } - fn fcursor(&mut self) -> SDSSResult { - vfs(&self.0, |f| Ok(f.pos)) - } -} - -type VirtualFS = VirtualFileInterface; -type RealFS = std::fs::File; +type VirtualFS = super::test_util::VirtualFS; mod rw { use crate::engine::storage::v1::{ @@ -213,8 +76,6 @@ mod tx { FileSpecifier, FileSpecifierVersion, HostRunMode, }; - type FileInterface = super::RealFS; - use { crate::{ engine::storage::v1::{ @@ -242,7 +103,7 @@ mod tx { } fn txn_reset( &self, - txn_writer: &mut JournalWriter, + txn_writer: &mut JournalWriter, ) -> SDSSResult<()> { self.reset(); txn_writer.append_event(TxEvent::Reset) @@ -254,7 +115,7 @@ mod tx { &self, pos: usize, val: u8, - txn_writer: &mut JournalWriter, + txn_writer: &mut JournalWriter, ) -> SDSSResult<()> { self.set(pos, val); txn_writer.append_event(TxEvent::Set(pos, val)) @@ -323,8 +184,8 @@ mod tx { fn open_log( log_name: &str, db: &Database, - ) -> SDSSResult> { - journal::open_journal::( + ) -> SDSSResult> { + journal::open_journal::( log_name, FileSpecifier::TestTransactionLog, FileSpecifierVersion::__new(0), @@ -355,7 +216,6 @@ mod tx { .append_journal_close_and_close() .unwrap(); assert_eq!(original_data, empty_db2.copy_data()); - std::fs::remove_file("testtxn.log").unwrap(); } #[test] fn oneboot_mod_twoboot_mod_thirdboot_read() { @@ -398,6 +258,5 @@ mod tx { .collect::>() .as_ref() ); - std::fs::remove_file("duatxn.db-tlog").unwrap(); } }