/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::std::mock::{mock_key, try_hook, MockKey}; use std::collections::HashMap; use std::ffi::OsString; use std::io::{ErrorKind, Read, Result, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::SystemTime;
/// The content of a mock filesystem item. pubenum MockFSContent { /// File content.
File(Result<MockFileContent>), /// A directory with the given entries.
Dir(MockDirEntries),
}
/// A mock filesystem item. #[derive(Debug)] pubstruct MockFSItem { /// The content of the item (file/dir). pub content: MockFSContent, /// The modification time of the item. pub modified: SystemTime,
}
/// Add a mocked file with the given content. The modification time will be the unix epoch. /// /// Pancis if the parent directory is not already mocked. pubfn add_file<P: AsRef<Path>, C: Into<MockFileContent>>(&self, path: P, content: C) -> &Self { self.add_file_result(path, Ok(content.into()), SystemTime::UNIX_EPOCH)
}
/// Add a mocked file that returns the given result and has the given modification time. /// /// Pancis if the parent directory is not already mocked. pubfn add_file_result<P: AsRef<Path>>(
&self,
path: P,
result: Result<MockFileContent>,
modified: SystemTime,
) -> &Self { let name = path.as_ref().file_name().expect("invalid path"); self.parent_dir(path.as_ref(), move |dir| { if dir.contains_key(name) {
Err(ErrorKind::AlreadyExists.into())
} else {
dir.insert(
name.to_owned(),
MockFSItem {
content: MockFSContent::File(result),
modified,
},
);
Ok(())
}
})
.and_then(|r| r)
.unwrap(); self
}
/// If create_dirs is true, all missing path components (_including the final component_) are /// created as directories. In this case `Err` is only returned if a file conflicts with /// a directory component. pubfn path<P: AsRef<Path>, F, R>(&self, path: P, create_dirs: bool, f: F) -> Result<R> where
F: FnOnce(&mut MockFSItem) -> R,
{ letmut guard = self.root.lock().unwrap(); letmut cur_entry = &mut *guard; for component in path.as_ref().components() { use std::path::Component::*; match component {
CurDir | RootDir | Prefix(_) => continue,
ParentDir => panic!("unsupported path: {}", path.as_ref().display()),
Normal(name) => { let cur_dir = match &mut cur_entry.content {
MockFSContent::File(_) => return Err(ErrorKind::NotFound.into()),
MockFSContent::Dir(d) => d,
};
cur_entry = if create_dirs {
cur_dir
.entry(name.to_owned())
.or_insert_with(|| MockFSContent::Dir(Default::default()).into())
} else {
cur_dir.get_mut(name).ok_or(ErrorKind::NotFound)?
};
}
}
}
Ok(f(cur_entry))
}
/// Get the mocked parent directory of the given path and call a callback on the mocked /// directory's entries. pubfn parent_dir<P: AsRef<Path>, F, R>(&self, path: P, f: F) -> Result<R> where
F: FnOnce(&mut MockDirEntries) -> R,
{ self.path(
path.as_ref().parent().unwrap_or(&Path::new("")), false, move |item| match &mut item.content {
MockFSContent::File(_) => Err(ErrorKind::NotFound.into()),
MockFSContent::Dir(d) => Ok(f(d)),
},
)
.and_then(|r| r)
}
/// Return a file assertion helper for the mocked filesystem. pubfn assert_files(&self) -> AssertFiles { letmut files = HashMap::new(); let root = self.root.lock().unwrap();
/// A utility for asserting the state of the mocked filesystem. /// /// All files must be accounted for; when dropped, a panic will occur if some files remain which /// weren't checked. #[derive(Debug)] pubstruct AssertFiles {
files: HashMap<PathBuf, MockFileContent>,
}
// On windows we ignore drive prefixes. This is only relevant for real paths, which are only // present for edge case situations in tests (where AssertFiles is used). fn remove_prefix(p: &Path) -> &Path { letmut iter = p.components(); iflet Some(std::path::Component::Prefix(_)) = iter.next() {
iter.next(); // Prefix is followed by RootDir
iter.as_path()
} else {
p
}
}
impl AssertFiles { /// Assert that the given path contains the given content (as a utf8 string). pubfn check<P: AsRef<Path>, S: AsRef<str>>(&mutself, path: P, content: S) -> &mutSelf { let p = remove_prefix(path.as_ref()); let Some(mfc) = self.files.remove(p) else {
panic!("missing file: {}", p.display());
}; let guard = mfc.0.lock().unwrap();
assert_eq!(
std::str::from_utf8(&*guard).unwrap(),
content.as_ref(), "file content mismatch: {}",
p.display()
); self
}
/// Assert that the given path contains the given byte content. pubfn check_bytes<P: AsRef<Path>, B: AsRef<[u8]>>(
&mutself,
path: P,
content: B,
) -> &mutSelf { let p = remove_prefix(path.as_ref()); let Some(mfc) = self.files.remove(p) else {
panic!("missing file: {}", p.display());
}; let guard = mfc.0.lock().unwrap();
assert_eq!(
&*guard,
content.as_ref(), "file content mismatch: {}",
p.display()
); self
}
/// Ignore the given file (whether it exists or not). pubfn ignore<P: AsRef<Path>>(&mutself, path: P) -> &mutSelf { self.files.remove(remove_prefix(path.as_ref())); self
}
/// Assert that the given path exists without checking its content. pubfn check_exists<P: AsRef<Path>>(&mutself, path: P) -> &mutSelf { let p = remove_prefix(path.as_ref()); ifself.files.remove(p).is_none() {
panic!("missing file: {}", p.display());
} self
}
/// Finish checking files. /// /// This panics if all files were not checked. /// /// This is also called when the value is dropped. pubfn finish(&mutself) { let files = std::mem::take(&mutself.files); if !files.is_empty() {
panic!("additional files not expected: {:?}", files.keys());
}
}
}
impl Drop for AssertFiles { fn drop(&mutself) { if !std::thread::panicking() { self.finish();
}
}
}
impl Seek for File { fn seek(&mutself, pos: SeekFrom) -> Result<u64> { let len = self.content.0.lock().unwrap().len(); match pos {
SeekFrom::Start(n) => self.pos = n as usize,
SeekFrom::End(n) => { if n < 0 { let offset = -n as usize; if offset > len { return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput, "out of bounds",
));
} self.pos = len - offset;
} else { self.pos = len + n as usize
}
}
SeekFrom::Current(n) => { if n < 0 { let offset = -n as usize; if offset > self.pos { return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput, "out of bounds",
));
} self.pos -= offset;
} else { self.pos += n as usize;
}
}
}
Ok(self.pos as u64)
}
}
impl Write for File { fn write(&mutself, buf: &[u8]) -> Result<usize> { if !self.write { return Err(std::io::ErrorKind::PermissionDenied.into());
} letmut guard = self.content.0.lock().unwrap(); let end = self.pos + buf.len(); if end > guard.len() {
guard.resize(end, 0);
}
(&mut guard[self.pos..end]).copy_from_slice(buf); self.pos = end;
Ok(buf.len())
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.