Compare commits

...

8 commits

Author SHA1 Message Date
8b613fae3c feat(fs): add lines to reader 2024-11-01 21:03:36 +00:00
8e624b4e63 feat(fs); add as_dir/as_file to convert from path 2024-11-01 21:03:36 +00:00
8aa8151253 test: remove unit tests
These have all been moved over to integration tests.
Integrtaion
2024-11-01 21:03:36 +00:00
e4877e2a99 refactor: split real module into sub-modules 2024-11-01 21:03:36 +00:00
5cf7b48857 refactor: move new fns to their struct 2024-11-01 21:03:26 +00:00
1ea9c1c557 test(fs): integration tests
Some checks failed
Rust / build (map[name:stable]) (push) Failing after 2m3s
Rust / build (map[name:nightly]) (push) Failing after 3m54s
2024-11-01 21:02:04 +00:00
b3118173ad feat(fs)!: remove legacy filesystem module
All checks were successful
Rust / build (map[name:stable]) (push) Successful in 2m8s
Rust / build (map[name:nightly]) (push) Successful in 2m16s
Release Please / Release-plz (push) Successful in 32s
2024-11-01 08:59:09 +00:00
b756881a60 refactor: extract result module
All checks were successful
Rust / build (map[name:nightly]) (push) Successful in 1m55s
Rust / build (map[name:stable]) (push) Successful in 2m5s
Release Please / Release-plz (push) Successful in 1m33s
2024-11-01 08:33:02 +00:00
16 changed files with 437 additions and 481 deletions

View file

@ -1,3 +1,8 @@
build:
cargo build
cargo test
cargo clippy
install-hooks:
@echo "Installing git hooks"
git config core.hooksPath .git-hooks

View file

@ -1,167 +0,0 @@
#![allow(deprecated)]
use std::{
ops::Deref,
path::PathBuf,
sync::{Arc, Mutex},
};
use tempfile::{tempdir, TempDir};
use tracing::{debug, info};
pub fn real(cwd: Option<PathBuf>) -> FileSystem {
let cwd = cwd.unwrap_or_default();
FileSystem::Real(RealFileSystem::new(cwd))
}
pub fn temp() -> std::io::Result<FileSystem> {
TempFileSystem::new().map(FileSystem::Temp)
}
#[derive(Clone, Debug)]
#[deprecated(since = "1.1.0", note = "Use [kxio::fs::FileSystem] instead")]
pub enum FileSystem {
Real(RealFileSystem),
Temp(TempFileSystem),
}
impl FileSystem {
#[deprecated(since = "1.1.0", note = "Use [kxio::filesystem::real()] instead")]
pub fn new_real(cwd: Option<PathBuf>) -> Self {
real(cwd)
}
#[deprecated(since = "1.1.0", note = "Use [kxio::filesystem::temp()] instead")]
pub fn new_temp() -> std::io::Result<Self> {
temp()
}
}
impl Deref for FileSystem {
type Target = dyn FileSystemLike;
fn deref(&self) -> &Self::Target {
match self {
Self::Real(env) => env,
Self::Temp(env) => env,
}
}
}
pub trait FileSystemLike: Sync + Send + std::fmt::Debug {
fn cwd(&self) -> &PathBuf;
fn in_cwd(&self, name: &str) -> PathBuf {
self.cwd().join(name)
}
fn write_file(&self, file_name: &str, content: &str) -> std::io::Result<PathBuf> {
use std::fs::File;
use std::io::{LineWriter, Write};
let path = self.in_cwd(file_name);
debug!("writing to {:?}", path);
let file = File::create(path.clone())?;
let mut file = LineWriter::new(file);
file.write_all(content.as_bytes())?;
Ok(path)
}
fn file_exists(&self, name: &PathBuf) -> bool {
use std::fs::File;
File::open(name).is_ok()
}
fn read_file(&self, file_name: &str) -> std::io::Result<String> {
use std::fs::File;
use std::io::Read;
let path = self.in_cwd(file_name);
debug!("reading from {:?}", path);
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
}
#[derive(Clone, Debug, Default)]
pub struct RealFileSystem {
cwd: PathBuf,
}
#[derive(Clone, Debug)]
pub struct TempFileSystem {
cwd: PathBuf,
// Handle to the temporary directory
// When this handle is dropped the directory is deleted
_temp_dir: Arc<Mutex<TempDir>>,
}
impl FileSystemLike for TempFileSystem {
fn cwd(&self) -> &PathBuf {
&self.cwd
}
}
impl FileSystemLike for RealFileSystem {
fn cwd(&self) -> &PathBuf {
&self.cwd
}
}
impl RealFileSystem {
const fn new(cwd: PathBuf) -> Self {
Self { cwd }
}
}
impl TempFileSystem {
fn new() -> std::io::Result<Self> {
let temp_dir = tempdir()?;
info!("temp dir: {:?}", temp_dir.path());
let cwd = temp_dir.path().to_path_buf();
let temp_dir = Arc::new(Mutex::new(temp_dir));
Ok(Self {
cwd,
_temp_dir: temp_dir,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test_log::test]
fn test_cwd() {
let cwd = PathBuf::from("/tmp");
let env = RealFileSystem::new(cwd.clone());
assert_eq!(env.cwd(), &cwd);
}
#[test_log::test]
fn test_create_on_temp_fs() -> std::io::Result<()> {
let env = TempFileSystem::new()?;
assert!(env.cwd().exists());
Ok(())
}
#[test_log::test]
fn test_create_on_real_fs() {
let cwd = PathBuf::from("/tmp");
let env = RealFileSystem::new(cwd.clone());
assert_eq!(env.cwd(), &cwd);
}
#[test_log::test]
fn test_write_and_read_file() -> std::io::Result<()> {
let env = TempFileSystem::new()?;
let file_name = "test.txt";
let content = "Hello, World!";
let path = env.write_file(file_name, content)?;
assert_eq!(env.read_file(file_name)?, content);
assert!(path.exists());
Ok(())
}
}

View file

@ -1,39 +1,18 @@
use std::path::PathBuf;
use derive_more::From;
mod dir_item;
mod real;
mod result;
mod temp;
mod dir_item;
pub use dir_item::DirItem;
pub use dir_item::DirItemIterator;
use real::FileSystem;
use temp::TempFileSystem;
pub use result::{Error, Result};
#[derive(Debug, From, derive_more::Display)]
pub enum Error {
Io(std::io::Error),
#[display("Path access attempted outside of base ({base:?}): {path:?}")]
PathTraversal {
base: PathBuf,
path: PathBuf,
},
#[display("Path must be a directory: {path:?}")]
NotADirectory {
path: PathBuf,
},
}
impl std::error::Error for Error {}
pub type Result<T> = core::result::Result<T, Error>;
pub const fn new(base: PathBuf) -> FileSystem {
real::new(base)
pub const fn new(base: PathBuf) -> real::FileSystem {
real::FileSystem::new(base)
}
pub fn temp() -> Result<TempFileSystem> {
temp::new()
pub fn temp() -> Result<temp::TempFileSystem> {
temp::TempFileSystem::new()
}

View file

@ -1,247 +0,0 @@
use std::{
fmt::Display,
path::{Path, PathBuf},
};
use crate::fs::DirItem;
use super::{DirItemIterator, Result};
pub const fn new(base: PathBuf) -> FileSystem {
FileSystem { base }
}
#[derive(Clone, Debug)]
pub struct FileSystem {
base: PathBuf,
}
impl FileSystem {
pub fn base(&self) -> &Path {
&self.base
}
pub fn path_of(&self, path: PathBuf) -> Result<PathBuf> {
let path_of = self.base.as_path().join(path);
self.validate(&path_of)?;
Ok(path_of)
}
pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> DirReal<'base, 'path> {
DirReal::new(self.base(), path)
}
pub fn file<'base, 'path>(&'base self, path: &'path Path) -> FileReal<'base, 'path> {
FileReal::new(self.base(), path)
}
pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path> {
PathReal::new(self.base(), path)
}
fn validate(&self, path: &Path) -> Result<()> {
let path = self.clean_path(path)?;
if !path.starts_with(&self.base) {
return Err(super::Error::PathTraversal {
base: self.base.clone(),
path,
});
}
Ok(())
}
fn clean_path(&self, path: &Path) -> Result<PathBuf> {
// let path = path.as_ref();
use path_clean::PathClean;
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
}
.clean();
Ok(abs_path)
}
}
pub struct DirReal<'base, 'path> {
path: PathReal<'base, 'path>,
}
impl<'base, 'path> DirReal<'base, 'path> {
fn new(base: &'base Path, path: &'path Path) -> Self {
let mut path = PathReal::new(base, path);
if path.error.is_none() {
if let Ok(exists) = path.exists() {
if exists {
if let Ok(is_dir) = path.is_dir() {
if !is_dir {
path.put(super::Error::NotADirectory {
path: path.full_path(),
})
}
}
}
}
}
Self { path }
}
pub fn create(&mut self) -> Result<()> {
self.path.check_error()?;
std::fs::create_dir(self.path.full_path()).map_err(Into::into)
}
pub fn create_all(&mut self) -> Result<()> {
self.path.check_error()?;
std::fs::create_dir_all(self.path.full_path()).map_err(Into::into)
}
pub fn read(&mut self) -> Result<Box<dyn Iterator<Item = Result<DirItem>>>> {
self.path.check_error()?;
let read_dir = std::fs::read_dir(self.path.full_path())?;
Ok(Box::new(DirItemIterator::new(read_dir)))
}
pub fn exists(&mut self) -> Result<bool> {
self.path.check_error()?;
self.path.exists()
}
pub fn is_dir(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(true)
}
pub fn is_file(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(false)
}
}
pub struct FileReal<'base, 'path> {
path: PathReal<'base, 'path>,
}
impl<'base, 'path> FileReal<'base, 'path> {
fn new(base: &'base Path, path: &'path Path) -> Self {
Self {
path: PathReal::new(base, path),
}
}
pub fn reader(&mut self) -> Result<ReaderReal> {
self.path.check_error()?;
ReaderReal::new(&self.path.full_path())
}
pub fn write(&mut self, contents: &str) -> Result<()> {
self.path.check_error()?;
std::fs::write(self.path.full_path(), contents).map_err(Into::into)
}
pub fn exists(&mut self) -> Result<bool> {
self.path.check_error()?;
self.path.exists()
}
pub fn is_dir(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(false)
}
pub fn is_file(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(true)
}
}
#[derive(Debug)]
pub struct PathReal<'base, 'path> {
base: &'base Path,
path: &'path Path,
error: Option<super::Error>,
}
impl<'base, 'path> PathReal<'base, 'path> {
fn full_path(&self) -> PathBuf {
self.base.join(self.path)
}
fn put(&mut self, error: super::Error) {
if self.error.is_none() {
self.error.replace(error);
}
}
fn validate(base: &Path, path: &Path) -> Option<super::Error> {
match PathReal::clean_path(path) {
Err(error) => Some(error),
Ok(path) => {
if !path.starts_with(base) {
return Some(super::Error::PathTraversal {
base: base.to_path_buf(),
path,
});
}
None
}
}
}
fn clean_path(path: &Path) -> Result<PathBuf> {
// let path = path.as_ref();
use path_clean::PathClean;
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir().expect("current_dir").join(path)
}
.clean();
Ok(abs_path)
}
fn new(base: &'base Path, path: &'path Path) -> Self {
Self {
base,
path,
error: PathReal::validate(base, path),
}
}
pub fn exists(&mut self) -> Result<bool> {
self.check_error()?;
Ok(self.full_path().exists())
}
fn check_error(&mut self) -> Result<()> {
if let Some(error) = self.error.take() {
return Err(error);
}
Ok(())
}
pub fn is_dir(&mut self) -> Result<bool> {
self.check_error()?;
Ok(self.full_path().is_dir())
}
pub fn is_file(&mut self) -> Result<bool> {
self.check_error()?;
Ok(self.full_path().is_file())
}
}
impl From<PathReal<'_, '_>> for PathBuf {
fn from(path: PathReal) -> Self {
path.base.join(path.path)
}
}
pub struct ReaderReal {
contents: String,
}
impl ReaderReal {
fn new(path: &Path) -> Result<Self> {
let contents = std::fs::read_to_string(path)?;
Ok(Self { contents })
}
pub fn as_str(&self) -> &str {
&self.contents
}
}
impl Display for ReaderReal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.contents)
}
}

64
src/fs/real/dir.rs Normal file
View file

@ -0,0 +1,64 @@
//
use std::path::{Path, PathBuf};
use crate::fs::{DirItem, DirItemIterator, Error, Result};
use super::path::PathReal;
pub struct DirReal<'base, 'path> {
path: PathReal<'base, 'path>,
}
impl<'base, 'path> DirReal<'base, 'path> {
pub(super) fn new(base: &'base Path, path: &'path Path) -> Self {
let mut path = PathReal::new(base, path);
if path.error.is_none() {
if let Ok(exists) = path.exists() {
if exists {
if let Ok(is_dir) = path.is_dir() {
if !is_dir {
path.put(Error::NotADirectory {
path: path.full_path(),
})
}
}
}
}
}
Self { path }
}
pub fn path(&self) -> PathBuf {
self.path.full_path()
}
pub fn create(&mut self) -> Result<()> {
self.path.check_error()?;
std::fs::create_dir(self.path.full_path()).map_err(Into::into)
}
pub fn create_all(&mut self) -> Result<()> {
self.path.check_error()?;
std::fs::create_dir_all(self.path.full_path()).map_err(Into::into)
}
pub fn read(&mut self) -> Result<Box<dyn Iterator<Item = Result<DirItem>>>> {
self.path.check_error()?;
let read_dir = std::fs::read_dir(self.path.full_path())?;
Ok(Box::new(DirItemIterator::new(read_dir)))
}
pub fn exists(&mut self) -> Result<bool> {
self.path.check_error()?;
self.path.exists()
}
pub fn is_dir(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(true)
}
pub fn is_file(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(false)
}
}

46
src/fs/real/file.rs Normal file
View file

@ -0,0 +1,46 @@
//
use std::path::{Path, PathBuf};
use crate::fs::Result;
use super::{path::PathReal, reader::ReaderReal};
pub struct FileReal<'base, 'path> {
path: PathReal<'base, 'path>,
}
impl<'base, 'path> FileReal<'base, 'path> {
pub(super) fn new(base: &'base Path, path: &'path Path) -> Self {
Self {
path: PathReal::new(base, path),
}
}
pub fn path(&self) -> PathBuf {
self.path.full_path()
}
pub fn reader(&mut self) -> Result<ReaderReal> {
self.path.check_error()?;
ReaderReal::new(&self.path.full_path())
}
pub fn write(&mut self, contents: &str) -> Result<()> {
self.path.check_error()?;
std::fs::write(self.path.full_path(), contents).map_err(Into::into)
}
pub fn exists(&mut self) -> Result<bool> {
self.path.check_error()?;
self.path.exists()
}
pub fn is_dir(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(false)
}
pub fn is_file(&mut self) -> Result<bool> {
self.path.check_error()?;
Ok(true)
}
}

8
src/fs/real/mod.rs Normal file
View file

@ -0,0 +1,8 @@
//
mod system;
mod path;
mod file;
mod dir;
mod reader;
pub use system::FileSystem;

102
src/fs/real/path.rs Normal file
View file

@ -0,0 +1,102 @@
//
use std::path::{Path, PathBuf};
use crate::fs::{Error, Result};
use super::{dir::DirReal, file::FileReal};
#[derive(Debug)]
pub struct PathReal<'base, 'path> {
base: &'base Path,
path: &'path Path,
pub(super) error: Option<Error>,
}
impl<'base, 'path> PathReal<'base, 'path> {
pub(super) fn full_path(&self) -> PathBuf {
self.base.join(self.path)
}
pub(super) fn put(&mut self, error: Error) {
if self.error.is_none() {
self.error.replace(error);
}
}
fn validate(base: &Path, path: &Path) -> Option<Error> {
match PathReal::clean_path(path) {
Err(error) => Some(error),
Ok(path) => {
if !path.starts_with(base) {
return Some(Error::PathTraversal {
base: base.to_path_buf(),
path,
});
}
None
}
}
}
fn clean_path(path: &Path) -> Result<PathBuf> {
// let path = path.as_ref();
use path_clean::PathClean;
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir().expect("current_dir").join(path)
}
.clean();
Ok(abs_path)
}
pub(super) fn new(base: &'base Path, path: &'path Path) -> Self {
Self {
base,
path,
error: PathReal::validate(base, path),
}
}
pub fn exists(&mut self) -> Result<bool> {
self.check_error()?;
Ok(self.full_path().exists())
}
pub(super) fn check_error(&mut self) -> Result<()> {
if let Some(error) = self.error.take() {
return Err(error);
}
Ok(())
}
pub fn is_dir(&mut self) -> Result<bool> {
self.check_error()?;
Ok(self.full_path().is_dir())
}
pub fn is_file(&mut self) -> Result<bool> {
self.check_error()?;
Ok(self.full_path().is_file())
}
pub fn as_dir(&mut self) -> Result<Option<DirReal<'base, 'path>>> {
self.check_error()?;
if self.full_path().is_dir() {
Ok(Some(DirReal::new(self.base, self.path)))
} else {
Ok(None)
}
}
pub fn as_file(&mut self) -> Result<Option<FileReal<'base, 'path>>> {
self.check_error()?;
if self.full_path().is_file() {
Ok(Some(FileReal::new(self.base, self.path)))
} else {
Ok(None)
}
}
}
impl From<PathReal<'_, '_>> for PathBuf {
fn from(path: PathReal) -> Self {
path.base.join(path.path)
}
}

31
src/fs/real/reader.rs Normal file
View file

@ -0,0 +1,31 @@
//
use std::{
fmt::Display,
path::Path, str::Lines,
};
use crate::fs::Result;
pub struct ReaderReal {
contents: String,
}
impl ReaderReal {
pub(super) fn new(path: &Path) -> Result<Self> {
let contents = std::fs::read_to_string(path)?;
Ok(Self { contents })
}
pub fn as_str(&self) -> &str {
&self.contents
}
pub fn lines(&self) -> Lines<'_> {
self.contents.lines()
}
}
impl Display for ReaderReal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.contents)
}
}

62
src/fs/real/system.rs Normal file
View file

@ -0,0 +1,62 @@
//
use std::path::{Path, PathBuf};
use crate::fs::{Error, Result};
use super::{dir::DirReal, file::FileReal, path::PathReal};
#[derive(Clone, Debug)]
pub struct FileSystem {
base: PathBuf,
}
impl FileSystem {
pub const fn new(base: PathBuf) -> Self {
Self { base }
}
pub fn base(&self) -> &Path {
&self.base
}
pub fn path_of(&self, path: PathBuf) -> Result<PathBuf> {
let path_of = self.base.as_path().join(path);
self.validate(&path_of)?;
Ok(path_of)
}
pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> DirReal<'base, 'path> {
DirReal::new(self.base(), path)
}
pub fn file<'base, 'path>(&'base self, path: &'path Path) -> FileReal<'base, 'path> {
FileReal::new(self.base(), path)
}
pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path> {
PathReal::new(self.base(), path)
}
fn validate(&self, path: &Path) -> Result<()> {
let path = self.clean_path(path)?;
if !path.starts_with(&self.base) {
return Err(Error::PathTraversal {
base: self.base.clone(),
path,
});
}
Ok(())
}
fn clean_path(&self, path: &Path) -> Result<PathBuf> {
// let path = path.as_ref();
use path_clean::PathClean;
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
}
.clean();
Ok(abs_path)
}
}

23
src/fs/result.rs Normal file
View file

@ -0,0 +1,23 @@
//
use std::path::PathBuf;
use derive_more::From;
#[derive(Debug, From, derive_more::Display)]
pub enum Error {
Io(std::io::Error),
#[display("Path access attempted outside of base ({base:?}): {path:?}")]
PathTraversal {
base: PathBuf,
path: PathBuf,
},
#[display("Path must be a directory: {path:?}")]
NotADirectory {
path: PathBuf,
},
}
impl std::error::Error for Error {}
pub type Result<T> = core::result::Result<T, Error>;

View file

@ -4,24 +4,24 @@ use tempfile::TempDir;
use super::real::FileSystem;
pub(super) fn new() -> super::Result<TempFileSystem> {
let temp_dir = tempfile::tempdir()?;
let base = temp_dir.path().to_path_buf();
let temp_dir = Arc::new(Mutex::new(temp_dir));
let real = super::real::new(base);
Ok(TempFileSystem {
real,
_temp_dir: temp_dir,
})
}
#[derive(Clone, Debug)]
pub struct TempFileSystem {
real: FileSystem,
_temp_dir: Arc<Mutex<TempDir>>,
}
impl TempFileSystem {
pub fn new() -> super::Result<Self> {
let temp_dir = tempfile::tempdir()?;
let base = temp_dir.path().to_path_buf();
let temp_dir = Arc::new(Mutex::new(temp_dir));
let real = super::new(base);
Ok(Self {
real,
_temp_dir: temp_dir,
})
}
}
impl std::ops::Deref for TempFileSystem {
type Target = FileSystem;

View file

@ -1,11 +1,6 @@
#[cfg(feature = "fs")]
pub mod filesystem;
//
#[cfg(feature = "fs")]
pub mod fs;
#[cfg(feature = "network")]
pub mod network;
#[cfg(test)]
mod tests;

View file

@ -1,12 +0,0 @@
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn write_read_file_exists() -> TestResult {
let fs = crate::filesystem::temp()?;
let pathbuf = fs.write_file("foo", "content")?;
let c = fs.read_file("foo")?;
assert_eq!(c, "content");
assert!(fs.file_exists(&pathbuf));
Ok(())
}

View file

@ -1,8 +0,0 @@
#[cfg(feature = "fs")]
pub mod filesystem;
#[cfg(feature = "fs")]
pub mod fs;
// #[cfg(feature = "network")]
// pub mod network;

View file

@ -1,6 +1,6 @@
use assert2::let_assert;
use crate::fs;
use kxio::fs;
type TestResult = Result<(), fs::Error>;
@ -32,6 +32,65 @@ mod path_of {
}
}
mod path {
use super::*;
#[test]
fn path_is_dir_as_dir_some() -> TestResult {
let fs = fs::temp().expect("temp fs");
let path = fs.base().join("foo");
let mut dir = fs.dir(&path);
dir.create().expect("create");
let_assert!(Ok(Some(as_dir)) = fs.path(&path).as_dir());
assert_eq!(dir.path(), as_dir.path());
Ok(())
}
#[test]
fn path_is_file_as_dir_none() -> TestResult {
let fs = fs::temp().expect("temp fs");
let path = fs.base().join("foo");
let mut file = fs.file(&path);
file.write("contents").expect("create");
let_assert!(Ok(Some(mut as_file)) = fs.path(&path).as_file());
assert_eq!(file.path(), as_file.path());
assert_eq!(as_file.reader().expect("reader").to_string(), "contents");
Ok(())
}
#[test]
fn path_is_dir_as_file_none() -> TestResult {
let fs = fs::temp().expect("temp fs");
let path = fs.base().join("foo");
let mut dir = fs.dir(&path);
dir.create().expect("create");
let_assert!(Ok(None) = fs.path(&path).as_file());
Ok(())
}
#[test]
fn path_is_file_as_file_some() -> TestResult {
let fs = fs::temp().expect("temp fs");
let path = fs.base().join("foo");
let mut file = fs.file(&path);
file.write("contents").expect("create");
let_assert!(Ok(None) = fs.path(&path).as_dir());
Ok(())
}
}
mod file {
use super::*;
@ -55,6 +114,22 @@ mod file {
Ok(())
}
#[test]
fn read_file_lines() -> TestResult {
let fs = fs::temp().expect("temp fs");
let path = fs.base().join("foo");
let mut file = fs.file(&path);
file.write("line 1\nline 2").expect("write");
let reader = file.reader().expect("reader");
let lines = reader.lines().collect::<Vec<_>>();
assert_eq!(lines, vec!["line 1", "line 2"]);
Ok(())
}
}
mod dir_create {