use crate::{
Configuration,
client::main_loop::Client,
config::Configuration_Optional,
protocol::{control::Compatibility, test_helpers::new_test_plumbing},
util::time::SystemTimeExt as _,
};
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget};
use littertray::LitterTray;
use rstest::*;
use std::{
cell::RefCell,
path::MAIN_SEPARATOR,
time::{Duration, SystemTime},
};
use walkdir::WalkDir;
const FILE_FOR_METADATA_TEST: &str = "src1/subdir/file2.txt";
#[allow(dead_code)]
const TEST_FILE_PERMISSIONS_MODE: u32 = 0o411;
fn test_modified_time() -> SystemTime {
SystemTime::from_unix(100_000)
}
#[allow(dead_code, reason = "Used on Unix, not on Windows")]
const TEST_DIR_PERMISSIONS_MODE: u32 = 0o515;
fn setup_fs(tray: &mut LitterTray) {
let _ = tray.make_dir("s/src1").unwrap();
let _ = tray.make_dir("s/src2").unwrap();
let _ = tray.make_dir("s/src3").unwrap();
#[allow(dead_code, reason = "Used on Unix, not on Windows")]
let d_perms = tray.make_dir("s/src1/subdir").unwrap();
let _ = tray
.create_text("s/src1/file1.txt", "file1 contents")
.unwrap();
let special_file = format!("s/{FILE_FOR_METADATA_TEST}");
let f_meta = tray.create_text(&special_file, "file2 contents").unwrap();
f_meta.set_modified(test_modified_time()).unwrap();
drop(f_meta);
let meta = std::fs::metadata(&special_file).unwrap();
let mut perms = meta.permissions();
#[cfg(windows)]
perms.set_readonly(true);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
perms.set_mode(TEST_FILE_PERMISSIONS_MODE);
}
std::fs::set_permissions(special_file, perms).unwrap();
let _ = tray
.create_text("s/src2/file3.txt", "file3 contents")
.unwrap();
let _ = tray
.create_text("s/src2/file4.txt", "file3 contents")
.unwrap();
let _ = tray
.create_text("s/src3/file5.txt", "file4 contents")
.unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
let meta = std::fs::metadata(&d_perms).unwrap();
let mut perms = meta.permissions();
perms.set_mode(TEST_DIR_PERMISSIONS_MODE);
std::fs::set_permissions(d_perms, perms).unwrap();
}
}
const ALL_SOURCES: &[&str] = &["s/src1", "s/src2", "s/src3"];
const EXPECTED_CONTENTS: &[&[&str]] = &[
&[""], &[
"src1",
"src1/subdir",
"src1/file1.txt",
"src1/subdir/file2.txt",
],
&["src2", "src2/file3.txt", "src2/file4.txt"],
];
const OUTPUT_DIRECTORY: &str = "d/outdir";
struct LocalTracing {}
#[fixture]
fn shared_setup_tracing() -> LocalTracing {
use crate::util::{ConsoleTraceType, TimeFormat, setup_tracing};
let _ = setup_tracing(
"debug",
ConsoleTraceType::Standard,
None,
TimeFormat::default(),
true,
);
LocalTracing {}
}
async fn run_plumbing(uut: &mut Client) -> anyhow::Result<(bool, crate::session::CommandStats)> {
let cfg = Configuration_Optional::default();
let n_streams = 10;
let client_streams = RefCell::new(Vec::with_capacity(n_streams));
let handlers = RefCell::new(Vec::with_capacity(n_streams));
{
let mut v_client = client_streams.borrow_mut();
let mut v_handlers = handlers.borrow_mut();
for idx in 0..n_streams {
let (p1, p2) = new_test_plumbing();
v_client.push(p1);
let jh = tokio::spawn(async move {
if let Err(e) = crate::server::handle_stream(
p2,
Compatibility::Level(4),
Configuration::system_default(),
)
.await
{
eprintln!("stream handler {idx} failed: {e}");
} else {
eprintln!("stream handler {idx} done");
}
});
v_handlers.push(jh);
}
v_client.reverse(); }
let prep_result = uut.prep(&cfg, Configuration::system_default()).unwrap();
uut.process_recursive_get(
&prep_result.job_specs,
async || {
Ok(client_streams
.borrow_mut()
.pop()
.expect("Ran out of streams!"))
},
|stream_pair, job, filename_width, pass| {
uut.run_request(stream_pair, job, filename_width, pass)
},
)
.await
}
#[rstest]
#[timeout(Duration::from_secs(1))]
#[tokio::test]
async fn get_multi(
#[allow(unused_variables)] shared_setup_tracing: LocalTracing,
#[values(1, 2)] n_sources: usize,
#[values(true, false)] dest_exists: bool,
#[values(true, false)] src_absolute: bool,
#[values(true, false)] dest_absolute: bool,
#[values(true, false)] preserve_metadata: bool,
) {
let should_succeed = n_sources == 1 || dest_exists;
LitterTray::try_with_async(async move |tray| {
setup_fs(tray);
if dest_exists {
let _ = tray.make_dir(OUTPUT_DIRECTORY)?;
}
let tray = tray.directory().to_str().unwrap();
let sources = ALL_SOURCES[..n_sources]
.iter()
.map(|s| {
if src_absolute {
format!("{tray}/{s}")
} else {
(*s).to_string()
}
})
.map(|s| format!("127.0.0.1:{s}"))
.collect::<Vec<_>>();
let sources_strs = sources
.iter()
.map(std::string::String::as_str)
.collect::<Vec<&str>>();
let dest = if dest_absolute {
format!("{tray}/{OUTPUT_DIRECTORY}")
} else {
OUTPUT_DIRECTORY.to_string()
};
let mut uut = super::make_uut_multi(|_, _| (), &sources_strs, &dest, 4);
uut.args.client_params.remote_debug = true;
uut.args.client_params.preserve = preserve_metadata;
uut.display = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
uut.spinner = ProgressBar::hidden();
uut.args.client_params.recurse = true;
let result = run_plumbing(&mut uut).await;
if should_succeed {
use std::collections::HashSet;
let result = result.expect("This operation should have succeeded");
eprintln!("result: {result:?}");
let mut raw = vec![];
for this_level in EXPECTED_CONTENTS.iter().take(n_sources + 1) {
let mut tmp = this_level.iter().collect::<Vec<_>>();
raw.append(&mut tmp);
}
let mut expected = HashSet::new();
for ent in &raw {
let mut s = String::from(OUTPUT_DIRECTORY);
if !ent.is_empty() {
if n_sources == 1 && !dest_exists {
if let Some((_, leaf)) = ent.split_once('/') {
s.push('/');
s.push_str(leaf);
} else {
}
} else {
s.push('/');
s.push_str(ent);
}
}
let _ = expected.insert(s); }
let walk = WalkDir::new(OUTPUT_DIRECTORY);
let mut actual = HashSet::new();
for ent in walk {
let ent = ent.unwrap();
let path = ent.path().to_str().unwrap().to_string();
let path = path.replace(MAIN_SEPARATOR, "/");
assert!(actual.insert(path));
}
assert_eq!(expected, actual, "expected set != actual");
} else {
let _ = result.expect_err("This operation should have failed");
}
if should_succeed && preserve_metadata {
let out_special_file = if dest_exists {
"d/outdir/src1/subdir/file2.txt"
} else {
"d/outdir/subdir/file2.txt"
};
let meta = std::fs::metadata(out_special_file).unwrap();
let perms = meta.permissions();
assert!(
perms.readonly(),
"Expected file with readonly permissions was not ({perms:?})"
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
assert_eq!(
perms.mode() & 0o777,
TEST_FILE_PERMISSIONS_MODE,
"Expected permissions {TEST_FILE_PERMISSIONS_MODE:o} were not found on test file (got {:o})",
perms.mode()
);
let dir_for_perms_check = if dest_exists {
"d/outdir/src1/subdir"
} else {
"d/outdir/subdir"
};
let dir_perms = std::fs::metadata(dir_for_perms_check).unwrap().permissions();
assert_eq!(dir_perms.mode() & 0o777, TEST_DIR_PERMISSIONS_MODE,
"Expected permissions {TEST_DIR_PERMISSIONS_MODE:o} were not found on test file (got {:o})",
dir_perms.mode()
);
}
assert_eq!(meta.modified().unwrap(), test_modified_time());
}
Ok(())
})
.await
.unwrap();
}
#[rstest]
#[timeout(Duration::from_secs(1))]
#[tokio::test]
async fn get_multi_single_file(
#[allow(unused_variables)] shared_setup_tracing: LocalTracing,
#[values(true, false)] dest_exists: bool,
) {
LitterTray::try_with_async(async move |tray| {
use std::path::MAIN_SEPARATOR_STR;
setup_fs(tray);
if dest_exists {
let _ = tray.make_dir(OUTPUT_DIRECTORY)?;
} else {
let _ = tray.make_dir("d");
}
let sources = vec!["127.0.0.1:s/src1/file1.txt"];
let src_file = "s/src1/file1.txt".replace('/', MAIN_SEPARATOR_STR);
let dest = OUTPUT_DIRECTORY.to_string();
let mut uut = super::make_uut_multi(|_, _| (), &sources, &dest, 4);
uut.args.client_params.remote_debug = true;
uut.display = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
uut.spinner = ProgressBar::hidden();
uut.args.client_params.recurse = true;
let result = run_plumbing(&mut uut).await;
let result = result.expect("This operation should have succeeded");
eprintln!("result: {result:?}");
let src_meta = std::fs::metadata(src_file).unwrap();
let expected_file_path = if dest_exists {
format!("{OUTPUT_DIRECTORY}{MAIN_SEPARATOR}file1.txt")
} else {
dest
};
let meta = std::fs::metadata(&expected_file_path).unwrap();
assert_eq!(meta.len(), src_meta.len());
Ok(())
})
.await
.unwrap();
}