more unit testing,, aaa

This commit is contained in:
Rowan 2025-03-03 19:03:55 -06:00
parent cd0a1527f2
commit af4af462bd
5 changed files with 337 additions and 185 deletions

View file

@ -58,7 +58,7 @@ pub fn as_char_derive(input: TokenStream) -> TokenStream {
fn from_char(c: char) -> Result<Self, Self::Err> {
match c {
#(#from_char_variants)*
ch => Err(char_enum::FromCharError::new(format!("{ch} is not a valid variant. expected one of: #(#chars)*")))
ch => Err(char_enum::FromCharError(ch))
}
}
}
@ -69,7 +69,7 @@ pub fn as_char_derive(input: TokenStream) -> TokenStream {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.chars().next() {
Some(c) => Ok(Self::from_char(c)?),
None => Err(char_enum::FromStrError::new(format!("{s} does not correspond to a valid #name variant"))),
None => Err(char_enum::FromStrError(s.to_string())),
}
}
}

View file

@ -1,4 +1,5 @@
use char_enum::{char_enum_derive::FromChar, FromChar, FromStrError, ToChar};
use std::ops::Deref;
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
use itertools::{peek_nth, Itertools};
@ -135,6 +136,12 @@ impl From<TypedIdRange> for UntypedIdRange {
}
}
impl From<(usize, usize, usize)> for UntypedIdRange {
fn from(value: (usize, usize, usize)) -> Self {
Self(value.0, value.1, value.2)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIdRangeError(String);
@ -278,9 +285,10 @@ impl FromStr for IdMapping {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match split(s, &[',', ' ']) {
Some((d, iter)) => Ok(Self::from_iter(iter)?.with_delimiter(d)),
None => Err(ParseIdRangeError(
"none of the provided delimiters matched string".to_string(),
)),
None => {
let s = std::slice::from_ref(&s).iter().map(Deref::deref);
Ok(Self::from_iter(s)?)
}
}
}
}

View file

@ -219,9 +219,7 @@ impl FromStr for SymbolicArgs {
Ok(perms) if perms.0.bits() > 0 => Ok(Self::Mode(perms)),
Ok(_) | Err(_) => match Modes::<GroupId>::from_str(s) {
Ok(perms) if perms.0.bits() > 0 => Ok(Self::Group(perms)),
Ok(_) | Err(_) => {
Err(FromStrError::new(format!("{} is not a valid argument", s)).into())
}
Ok(_) | Err(_) => Err(FromStrError(s.to_string()).into()),
},
}
}

View file

@ -6,11 +6,12 @@ use std::{
ops::Deref,
path::{Path, PathBuf},
process::Command,
slice::Iter,
str::FromStr,
};
use crate::fs::{
id_mapping::{IdMapping, ParseIdRangeError},
id_mapping::{IdMapping as OriginalIdMapping, IdRange, ParseIdRangeError},
mount::MountOptions,
AsIter, FileSystem, Mountpoint,
};
@ -18,8 +19,8 @@ use crate::macros::*;
use super::Stack;
macro_rules! impl_wrapper {
($name:ident($inner:ty), $err:ty, $from_str:block) => {
macro_rules! impl_deref {
($name:ident($inner:ty)) => {
impl Deref for $name {
type Target = $inner;
@ -27,16 +28,23 @@ macro_rules! impl_wrapper {
&self.0
}
}
};
}
macro_rules! impl_fromstr {
($name:ident($inner:ty), $err:ty) => {
impl FromStr for $name {
type Err = $err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
$from_str
//Ok(Self($target::from_str(s)?))
Ok(Self(<$inner>::from_str(s)?))
}
}
};
}
macro_rules! impl_display {
($name:ident) => {
impl Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
@ -45,12 +53,13 @@ macro_rules! impl_wrapper {
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Test(usize);
impl_wrapper!(Test(usize), ParseIntError, {
Ok(Test(usize::from_str(s)?))
});
macro_rules! impl_wrapper_struct {
($name:ident($inner:ty), $err:ty) => {
impl_deref!($name($inner));
impl_fromstr!($name($inner), $err);
impl_display!($name);
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseMaxIdleThreadsError(ParseIntError);
@ -85,9 +94,39 @@ impl_wrapper_err_for!(ParseSquashToGidError(ParseSquashToIdError), "invalid gid"
impl_wrapper_err_for!(ParseUidMappingError(ParseIdRangeError), "invalid uid");
impl_wrapper_err_for!(ParseGidMappingError(ParseIdRangeError), "invalid gid");
#[derive(Clone, Debug, Default)]
pub struct DirIter<'a> {
inner: Iter<'a, PathBuf>,
}
impl<'a> Iterator for DirIter<'a> {
type Item = &'a Path;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(PathBuf::as_path)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LowerDirs(Vec<PathBuf>);
impl<A: Into<PathBuf>> FromIterator<A> for LowerDirs {
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
Self(iter.into_iter().map(Into::into).collect())
}
}
impl<'a> IntoIterator for &'a LowerDirs {
type Item = &'a Path;
type IntoIter = DirIter<'a>;
fn into_iter(self) -> Self::IntoIter {
DirIter {
inner: self.as_slice().iter(),
}
}
}
impl Deref for LowerDirs {
type Target = Vec<PathBuf>;
@ -98,14 +137,16 @@ impl Deref for LowerDirs {
impl Display for LowerDirs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|v| v.to_string_lossy())
.fold(String::new(), |a, b| a + &b)
)
let mut iter = self.0.iter();
if let Some(head) = iter.next() {
write!(f, "{}", head.to_string_lossy())?;
for item in iter {
write!(f, ",{}", item.to_string_lossy())?;
}
}
Ok(())
}
}
@ -113,7 +154,12 @@ impl FromStr for LowerDirs {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.split(',').map(Into::into).collect()))
Ok(Self(
s.split(',')
.filter(|s| !s.is_empty())
.map(Into::into)
.collect(),
))
}
}
@ -141,22 +187,16 @@ impl From<Vec<PathBuf>> for LowerDirs {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct MaxIdleThreads(Option<usize>);
impl Default for MaxIdleThreads {
fn default() -> Self {
Self(None)
}
}
impl FromStr for MaxIdleThreads {
type Err = ParseMaxIdleThreadsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"-1" => Ok(Self(None)),
s => Ok(usize::from_str_radix(s, 10).map(Some).map(Self)?),
s => Ok(s.parse::<usize>().map(Some).map(Self)?),
}
}
}
@ -170,6 +210,8 @@ impl Display for MaxIdleThreads {
}
}
impl_deref!(MaxIdleThreads(Option<usize>));
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MaxThreads(usize);
@ -187,127 +229,59 @@ impl FromStr for MaxThreads {
}
}
impl Display for MaxThreads {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl_deref!(MaxThreads(usize));
impl_display!(MaxThreads);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SquashToId(usize);
impl_deref!(SquashToId(usize));
impl_display!(SquashToId);
impl FromStr for SquashToId {
type Err = ParseSquashToIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(usize::from_str_radix(s, 10).map(Self)?)
}
}
impl Display for SquashToId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
Ok(Self(s.parse::<usize>()?))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SquashToUid(SquashToId);
impl Deref for SquashToUid {
type Target = SquashToId;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for SquashToUid {
type Err = ParseSquashToUidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(SquashToId::from_str(s)?))
}
}
impl Display for SquashToUid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl_wrapper_struct!(SquashToUid(SquashToId), ParseSquashToUidError);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SquashToGid(SquashToId);
impl Deref for SquashToGid {
type Target = SquashToId;
impl_wrapper_struct!(SquashToGid(SquashToId), ParseSquashToGidError);
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for SquashToGid {
type Err = ParseSquashToGidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(SquashToId::from_str(s)?))
}
}
impl Display for SquashToGid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GidMapping(IdMapping);
pub struct IdMapping(OriginalIdMapping);
impl Deref for GidMapping {
type Target = IdMapping;
fn deref(&self) -> &Self::Target {
&self.0
impl IdMapping {
pub fn new(ranges: impl IntoIterator<Item = impl Into<IdRange>>) -> Self {
Self(OriginalIdMapping::new(ranges.into_iter(), ','))
}
}
impl FromStr for GidMapping {
type Err = ParseGidMappingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(IdMapping::from_str(s)?))
impl From<OriginalIdMapping> for IdMapping {
fn from(value: OriginalIdMapping) -> Self {
Self(value)
}
}
impl Display for GidMapping {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl_wrapper_struct!(IdMapping(OriginalIdMapping), ParseIdRangeError);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UidMapping(IdMapping);
impl_wrapper_struct!(UidMapping(IdMapping), ParseUidMappingError);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GidMapping(IdMapping);
impl Deref for GidMapping {
type Target = IdMapping;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for GidMapping {
type Err = ParseGidMappingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(IdMapping::from_str(s)?))
}
}
impl Display for GidMapping {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl_wrapper_struct!(GidMapping(IdMapping), ParseGidMappingError);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseFuseOverlayOptionError {
@ -317,6 +291,7 @@ pub enum ParseFuseOverlayOptionError {
SquashToGid(ParseSquashToGidError),
UidMapping(ParseUidMappingError),
GidMapping(ParseGidMappingError),
UnknownOption(String),
}
impl Display for ParseFuseOverlayOptionError {
@ -328,6 +303,7 @@ impl Display for ParseFuseOverlayOptionError {
Self::SquashToGid(e) => write!(f, "{e}"),
Self::UidMapping(e) => write!(f, "{e}"),
Self::GidMapping(e) => write!(f, "{e}"),
Self::UnknownOption(opt) => write!(f, "unknown option: {opt}"),
}
}
}
@ -337,7 +313,11 @@ impl Error for ParseFuseOverlayOptionError {}
impl_from_variants!(
ParseFuseOverlayOptionError,
MaxIdleThreads(ParseMaxIdleThreadsError),
MaxThreads(ParseMaxThreadsError)
MaxThreads(ParseMaxThreadsError),
SquashToUid(ParseSquashToUidError),
SquashToGid(ParseSquashToGidError),
UidMapping(ParseUidMappingError),
GidMapping(ParseGidMappingError)
);
#[derive(Clone, Debug, PartialEq, Eq)]
@ -355,8 +335,8 @@ pub enum FuseOverlayOption {
SquashToGid(SquashToGid),
StaticNLink,
NoAcl,
UidMapping(IdMapping),
GidMapping(IdMapping),
UidMapping(UidMapping),
GidMapping(GidMapping),
}
impl_from_variants!(
@ -365,7 +345,9 @@ impl_from_variants!(
MaxIdleThreads(MaxIdleThreads),
MaxThreads(MaxThreads),
SquashToUid(SquashToUid),
SquashToGid(SquashToGid)
SquashToGid(SquashToGid),
UidMapping(UidMapping),
GidMapping(GidMapping)
);
impl FromStr for FuseOverlayOption {
@ -374,7 +356,7 @@ impl FromStr for FuseOverlayOption {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim().trim_start_matches('-');
let option = match s.split_once(|delim| delim == ' ' || delim == '=') {
let option = match s.split_once([' ', '=']) {
Some((option, value)) => (option, Some(value)),
None => (s, None),
};
@ -384,17 +366,18 @@ impl FromStr for FuseOverlayOption {
("upperdir", Some(args)) => Ok(Self::UpperDir(PathBuf::from(args))),
("workdir", Some(args)) => Ok(Self::WorkDir(PathBuf::from(args))),
("clonefd", None) => Ok(Self::CloneFd),
("max_idle_threads", Some(args)) => Ok(MaxIdleThreads::from_str(args).into()?),
("max_threads", Some(args)) => Ok(MaxThreads::from_str(args).into()?),
("max_idle_threads", Some(args)) => Ok(MaxIdleThreads::from_str(args)?.into()),
("max_threads", Some(args)) => Ok(MaxThreads::from_str(args)?.into()),
("allow_other", None) => Ok(Self::AllowOther),
("allow_root", None) => Ok(Self::AllowRoot),
("squash_to_root", None) => Ok(Self::SquashToRoot),
("squash_to_uid", Some(uid)) => Ok(SquashToUid::from_str(uid).into()?),
("squash_to_gid", Some(gid)) => Ok(SquashToGid::from_str(gid).into()?),
("squash_to_uid", Some(uid)) => Ok(SquashToUid::from_str(uid)?.into()),
("squash_to_gid", Some(gid)) => Ok(SquashToGid::from_str(gid)?.into()),
("static_nlink", None) => Ok(Self::StaticNLink),
("noacl", None) => Ok(Self::NoAcl),
("uidmapping", Some(ids)) => Ok(Self::UidMapping(IdMapping::from_str(ids)?)),
("gidmapping", Some(ids)) => Ok(Self::GidMapping(IdMapping::from_str(ids)?)),
("uidmapping", Some(ids)) => Ok(UidMapping::from_str(ids)?.into()),
("gidmapping", Some(ids)) => Ok(GidMapping::from_str(ids)?.into()),
(opt, _) => Err(ParseFuseOverlayOptionError::UnknownOption(opt.to_string())),
}
}
}
@ -404,9 +387,9 @@ impl Display for FuseOverlayOption {
write!(f, "-o ")?;
match self {
FuseOverlayOption::LowerDir(lower) => write!(f, "{lower}"),
FuseOverlayOption::UpperDir(path) => write!(f, "{}", path.to_string_lossy()),
FuseOverlayOption::WorkDir(path) => write!(f, "{}", path.to_string_lossy()),
FuseOverlayOption::LowerDir(lower) => write!(f, "lowerdir={lower}"),
FuseOverlayOption::UpperDir(path) => write!(f, "upperdir={}", path.to_string_lossy()),
FuseOverlayOption::WorkDir(path) => write!(f, "workdir={}", path.to_string_lossy()),
FuseOverlayOption::CloneFd => write!(f, "clone_fd"),
FuseOverlayOption::MaxIdleThreads(n) => write!(f, "max_idle_threads={n}"),
FuseOverlayOption::MaxThreads(n) => write!(f, "max_threads={n}"),
@ -425,47 +408,59 @@ impl Display for FuseOverlayOption {
#[derive(Debug)]
pub struct FuseOverlay {
lower_dir: usize,
upper_dir: Option<usize>,
work_dir: Option<usize>,
mount_target: PathBuf,
options: MountOptions<FuseOverlayOption>,
}
impl FuseOverlay {
pub fn new(lowerdir: impl Into<LowerDirs>) -> Self {
pub fn new(target: impl Into<PathBuf>) -> Self {
Self {
lower_dir: 0,
upper_dir: None,
work_dir: None,
options: MountOptions(vec![FuseOverlayOption::LowerDir(lowerdir.into())]),
mount_target: target.into(),
options: Default::default(),
}
}
pub fn push_option(&mut self, option: FuseOverlayOption) {
self.options.push(option);
}
pub fn push_options(&mut self, options: impl IntoIterator<Item = FuseOverlayOption>) {
self.options.extend(options)
}
fn find_option<'a, T, F>(&'a self, f: F) -> Option<T>
where
F: Fn(&'a FuseOverlayOption) -> Option<T>,
{
self.options.iter().filter_map(f).take(1).next()
}
}
impl Stack for FuseOverlay {
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
match self.options.get(self.lower_dir) {
Some(FuseOverlayOption::LowerDir(lower)) => lower.as_iter().map(AsRef::as_ref),
_ => panic!("invalid lowerdir option"),
let result = self.find_option(|opt| match opt {
FuseOverlayOption::LowerDir(dirs) => Some(dirs),
_ => None,
});
match result {
Some(dirs) => dirs.into_iter(),
None => DirIter::default(),
}
}
fn upper_dir(&self) -> Option<&std::path::Path> {
let upper = self.upper_dir.and_then(|i| self.options.get(i));
match upper {
Some(FuseOverlayOption::UpperDir(upper)) => Some(upper),
self.find_option(|opt| match opt {
FuseOverlayOption::UpperDir(dir) => Some(dir.as_path()),
_ => None,
}
})
}
fn work_dir(&self) -> Option<&std::path::Path> {
let work = self.work_dir.and_then(|i| self.options.get(i));
match work {
Some(FuseOverlayOption::WorkDir(work)) => Some(work),
self.find_option(|opt| match opt {
FuseOverlayOption::WorkDir(dir) => Some(dir.as_path()),
_ => None,
}
})
}
}
@ -477,29 +472,18 @@ impl Mountpoint for FuseOverlay {
impl FileSystem for FuseOverlay {
fn mount(&mut self) -> std::io::Result<()> {
let mut cmd = Command::new("fuse-overlayfs").arg(self.options.to_string());
if self.fs.lower.len() > 0 {
cmd.arg(format!("-o lowerdir={}", self.fs.lower));
};
if let Some(upper) = &self.fs.upper {
cmd.arg(format!("-o upperdir={}", upper.to_string_lossy()));
};
if let Some(work) = &self.fs.work {
cmd.arg(format!("-o workdir={}", work.to_string_lossy()));
};
cmd.output()?;
Command::new("fuse-overlayfs")
.arg(self.options.to_string())
.output()?;
Ok(())
}
fn unmount(&mut self) -> std::io::Result<()> {
if let Some(upper) = &self.fs.upper {
Command::new("fusermount").arg("-u").arg(upper).output()?;
}
Command::new("fusermount")
.arg("-u")
.arg(&self.mount_target)
.output()?;
Ok(())
}
@ -507,6 +491,165 @@ impl FileSystem for FuseOverlay {
impl Display for FuseOverlay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "fuse-overlayfs")
write!(f, "fuse-overlayfs")?;
// INFO: this is to get rid of the extraneous space if
// self.options is empty
if self.options.len() > 0 {
write!(f, " {}", self.options)?;
}
write!(f, " {}", self.mount_target.to_string_lossy())
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use std::str::FromStr;
use super::*;
use crate::fs::id_mapping::UntypedIdRange;
use crate::fs::stackable::Stack;
use crate::fs::Mountpoint;
#[test]
fn lower_dirs() {
let empty = LowerDirs::default();
assert_eq!(empty.to_string(), "");
let one = LowerDirs::from_iter(["/lower"]);
assert_eq!(one.to_string(), "/lower");
let two = LowerDirs::from_iter(["/first", "/second"]);
assert_eq!(two.to_string(), "/first,/second");
let from_str_none = LowerDirs::from_str("").unwrap();
assert_eq!(from_str_none, empty);
let from_str_one = LowerDirs::from_str("/lower").unwrap();
assert_eq!(from_str_one, one);
let from_str_two = LowerDirs::from_str("/first,/second").unwrap();
assert_eq!(from_str_two, two);
}
#[test]
fn max_threads() {
let default_threads = MaxThreads::default();
assert_eq!(*default_threads, 10);
assert_eq!(default_threads.to_string(), "10");
assert_eq!(MaxThreads::from_str("10").unwrap(), default_threads);
let err = MaxThreads::from_str("");
assert!(matches!(err, Err(ParseMaxThreadsError(_))));
assert_eq!(MaxThreads::from_str("69").unwrap(), MaxThreads(69));
}
#[test]
fn max_idle_threads() {
let default_threads = MaxIdleThreads::default();
assert_eq!(*default_threads, None);
assert_eq!(default_threads.to_string(), "-1");
assert_eq!(MaxIdleThreads::from_str("-1").unwrap(), default_threads);
assert_eq!(
MaxIdleThreads::from_str("69").unwrap(),
MaxIdleThreads(Some(69))
);
let err = MaxIdleThreads::from_str("");
assert!(matches!(err, Err(ParseMaxIdleThreadsError(_))));
}
#[test]
fn squash_to_id() {
let squash = SquashToId(1);
assert_eq!(squash.to_string(), "1");
assert_eq!(SquashToId::from_str("1").unwrap(), squash);
let err = SquashToId::from_str("");
assert!(matches!(err, Err(ParseSquashToIdError(_))));
let uid = SquashToUid(SquashToId(2));
let gid = SquashToGid(SquashToId(4));
assert_eq!(uid.to_string(), "2");
assert_eq!(gid.to_string(), "4");
assert_eq!(SquashToUid::from_str("2").unwrap(), uid);
assert_eq!(SquashToGid::from_str("4").unwrap(), gid);
let uid_err = SquashToUid::from_str("");
let gid_err = SquashToGid::from_str("");
assert!(matches!(uid_err, Err(ParseSquashToUidError(_))));
assert!(matches!(gid_err, Err(ParseSquashToGidError(_))));
}
#[test]
fn id_mapping() {
let id_range = IdRange::Untyped(UntypedIdRange(1, 1000, 1));
let uid = UidMapping(IdMapping::new([id_range.clone()]));
let gid = GidMapping(IdMapping::new([id_range.clone()]));
assert_eq!(uid.to_string(), "1:1000:1");
assert_eq!(gid.to_string(), "1:1000:1");
assert_eq!(UidMapping::from_str("1:1000:1").unwrap(), uid);
assert_eq!(GidMapping::from_str("1:1000:1").unwrap(), gid);
}
#[test]
fn command_output() {
let basic = FuseOverlay::new("/mnt");
assert_eq!(basic.to_string(), "fuse-overlayfs /mnt");
let mut with_options = FuseOverlay::new("~/.config");
with_options.push_options([
FuseOverlayOption::NoAcl,
LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(),
]);
assert_eq!(
with_options.to_string(),
"fuse-overlayfs -o noacl -o lowerdir=~/.themes,~/.colors ~/.config"
);
}
#[test]
fn mountpoint_trait_impl() {
let mut all = FuseOverlay::new("/mnt");
all.push_options([
LowerDirs::from_iter(["/doesnt", "/matter"]).into(),
FuseOverlayOption::UpperDir("/upper".into()),
FuseOverlayOption::WorkDir("/work".into()),
]);
let opts: Vec<String> = all.options().map(|x| x.to_string()).collect();
assert_eq!(
opts,
[
"-o lowerdir=/doesnt,/matter",
"-o upperdir=/upper",
"-o workdir=/work"
]
);
}
#[test]
fn stack_trait_impl() {
let mut all = FuseOverlay::new("/mnt");
assert_eq!(all.lower_dirs().next(), None);
assert_eq!(all.upper_dir(), None);
assert_eq!(all.work_dir(), None);
all.push_option(LowerDirs::from_iter(["/lower"]).into());
assert_eq!(all.lower_dirs().next(), Some(Path::new("/lower")));
all.push_option(FuseOverlayOption::UpperDir("/upper".into()));
assert_eq!(all.upper_dir(), Some(Path::new("/upper")));
all.push_option(FuseOverlayOption::WorkDir("/work".into()));
assert_eq!(all.work_dir(), Some(Path::new("/work")));
}
}

View file

@ -1,8 +1,10 @@
use std::fmt::Display;
use std::iter;
use std::path::PathBuf;
use crate::fs::{AsIter, Mountpoint};
use super::{Stack, Stackable};
use super::Stack;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum RedirectDir {
@ -96,21 +98,22 @@ impl Display for OverlayOptions {
#[derive(Debug)]
pub struct Overlay {
fs: Stackable,
mount_target: PathBuf,
options: Vec<OverlayOptions>,
}
impl Stack for Overlay {
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
self.fs.lower_dirs()
todo!();
iter::empty()
}
fn upper_dir(&self) -> Option<&std::path::Path> {
self.fs.upper_dir()
todo!()
}
fn work_dir(&self) -> Option<&std::path::Path> {
self.fs.work_dir()
todo!()
}
}