use itertools::Itertools;
use serde::{Serialize, Serializer};
use std::cell::Cell;
pub struct IteratorWrapper<T>(pub Cell<Option<T>>);
impl<I, P> Serialize for IteratorWrapper<I>
where
I: IntoIterator<Item = P>,
P: Serialize,
{
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.collect_seq(self.0.take().unwrap())
}
}
pub fn try_sort_by_key<T, K, E>(v: &mut [T], f: impl Fn(&T) -> Result<K, E>) -> Vec<E>
where
K: Ord,
{
let mut errors: Vec<E> = Vec::new();
v.sort_by_key(|t| f(t).map_err(|e| errors.push(e)).ok());
errors
}
pub fn reduce_results<I, T, E, F>(mut iter: I, f: F) -> Result<Option<T>, E>
where
I: Iterator<Item = Result<T, E>>,
F: Fn(T, T) -> T,
{
iter.fold_ok(None, |res, item| match res {
Some(res) => Some(f(res, item)),
None => Some(item),
})
}
pub fn min_result<I, T, E>(iter: I) -> Result<Option<T>, E>
where
I: Iterator<Item = Result<T, E>>,
T: Ord,
{
reduce_results(iter, T::min)
}
pub fn max_result<I, T, E>(iter: I) -> Result<Option<T>, E>
where
I: Iterator<Item = Result<T, E>>,
T: Ord,
{
reduce_results(iter, T::max)
}
#[cfg(test)]
pub mod test {
use std::fs::{create_dir_all, remove_dir_all, File};
use std::io::{BufReader, Read, Write};
use std::path::PathBuf;
use std::sync::Mutex;
use std::time::SystemTime;
use std::{fs, thread};
use super::*;
use lazy_static::lazy_static;
#[derive(Debug, PartialEq, Eq)]
enum FsSupportsReflink {
Untested,
Supported(bool),
}
lazy_static! {
static ref REFLINK_SUPPORTED: Mutex<FsSupportsReflink> =
Mutex::new(FsSupportsReflink::Untested);
}
pub fn with_dir<F, R>(test_root: &str, test_code: F) -> R
where
F: FnOnce(&PathBuf) -> R,
{
let test_root = PathBuf::from("target/test").join(test_root);
if test_root.is_absolute() && !test_root.starts_with("/dev/shm/") {
panic!("Internal test error: only use relative paths!");
}
remove_dir_all(&test_root).ok();
create_dir_all(&test_root).unwrap();
let ret = test_code(&test_root.canonicalize().unwrap());
remove_dir_all(&test_root).ok();
ret
}
pub fn create_file(path: &std::path::Path) {
let _ = fs::remove_file(path);
File::create(path).unwrap();
}
pub fn create_file_newer_than(f: &PathBuf, time: SystemTime) -> SystemTime {
assert!(SystemTime::now() >= time);
let mut delay = std::time::Duration::from_millis(1);
loop {
thread::sleep(delay);
create_file(f);
let ctime = fs::metadata(f).unwrap().modified().unwrap();
if ctime != time {
return ctime;
}
delay *= 2;
}
}
pub fn write_file(path: &std::path::Path, content: &str) {
let mut f = File::create(path).unwrap();
write!(&mut f, "{content}").unwrap();
}
pub fn read_file(path: &std::path::Path) -> String {
let f = File::open(path).unwrap();
let mut r = BufReader::new(f);
let mut result = String::new();
r.read_to_string(&mut result).unwrap();
result
}
pub fn cached_reflink_supported() -> bool {
let mut guard = REFLINK_SUPPORTED.lock().unwrap();
match *guard {
FsSupportsReflink::Untested => {
with_dir("fs_supports_reflink", |test_dir| {
let src_file = test_dir.join("src_file");
let dest_file = test_dir.join("dest_file");
write_file(&src_file, "1");
let result = reflink::reflink(src_file, dest_file).is_ok();
*guard = FsSupportsReflink::Supported(result);
if !result {
println!(" Notice: filesystem does not support reflinks, skipping related tests")
}
result
})
}
FsSupportsReflink::Supported(val) => val,
}
}
#[test]
fn min_result_should_return_none_if_no_elements() {
let elements: Vec<Result<i64, &str>> = vec![];
assert_eq!(min_result(elements.into_iter()), Ok(None));
}
#[test]
fn min_result_should_return_min_if_all_ok() {
let elements: Vec<Result<i64, &str>> = vec![Ok(1), Ok(3), Ok(2)];
assert_eq!(min_result(elements.into_iter()), Ok(Some(1)));
}
#[test]
fn min_result_should_return_err_if_at_least_one_err() {
let elements: Vec<Result<i64, &str>> = vec![Ok(1), Ok(3), Err("error"), Ok(2)];
assert_eq!(min_result(elements.into_iter()), Err("error"));
}
#[test]
fn max_result_should_return_max_if_all_ok() {
let elements: Vec<Result<i64, &str>> = vec![Ok(1), Ok(3), Ok(2)];
assert_eq!(max_result(elements.into_iter()), Ok(Some(3)));
}
}