Fix many tests and write implementations for fs exists/metadata/watch

pull/172/head
Chip Senkbeil 1 year ago
parent f6ce46df12
commit 759394d0e1
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

@ -35,8 +35,6 @@ distant-core = "0.19"
Currently, the library supports the following features:
- `clap`: generates [`Clap`](https://github.com/clap-rs) bindings for
`DistantRequestData` (used by cli to expose request actions)
- `schemars`: derives the `schemars::JsonSchema` interface on
`DistantMsg`, `DistantRequestData`, and `DistantResponseData` data types

@ -61,8 +61,8 @@ impl Watcher {
DistantRequestData::Watch {
path: path.to_path_buf(),
recursive,
only: only.into_vec(),
except: except.into_vec(),
only: only.into_sorted_vec(),
except: except.into_sorted_vec(),
},
)))
.await?;

@ -280,7 +280,6 @@ pub enum DistantRequestData {
},
/// Moves/renames a file or directory on the remote machine
#[cfg_attr(feature = "clap", clap(visible_aliases = &["mv"]))]
#[strum_discriminants(strum(message = "Supports renaming files, directories, and symlinks"))]
Rename {
/// The path to the file or directory on the remote machine
@ -299,23 +298,14 @@ pub enum DistantRequestData {
/// If true, will recursively watch for changes within directories, othewise
/// will only watch for changes immediately within directories
#[serde(default)]
#[cfg_attr(feature = "clap", clap(long))]
recursive: bool,
/// Filter to only report back specified changes
#[serde(default)]
#[cfg_attr(
feature = "clap",
clap(long, value_parser = clap::builder::PossibleValuesParser::new(ChangeKind::VARIANTS))
)]
only: Vec<ChangeKind>,
/// Filter to report back changes except these specified changes
#[serde(default)]
#[cfg_attr(
feature = "clap",
clap(long, value_parser = clap::builder::PossibleValuesParser::new(ChangeKind::VARIANTS))
)]
except: Vec<ChangeKind>,
},
@ -345,12 +335,10 @@ pub enum DistantRequestData {
/// returning the canonical, absolute form of a path with all
/// intermediate components normalized and symbolic links resolved
#[serde(default)]
#[cfg_attr(feature = "clap", clap(long))]
canonicalize: bool,
/// Whether or not to follow symlinks to determine absolute file type (dir/file)
#[serde(default)]
#[cfg_attr(feature = "clap", clap(long))]
resolve_file_type: bool,
},
@ -358,7 +346,6 @@ pub enum DistantRequestData {
#[strum_discriminants(strum(message = "Supports searching filesystem using queries"))]
Search {
/// Query to perform against the filesystem
#[cfg_attr(feature = "clap", clap(flatten))]
query: SearchQuery,
},
@ -372,31 +359,25 @@ pub enum DistantRequestData {
},
/// Spawns a new process on the remote machine
#[cfg_attr(feature = "clap", clap(visible_aliases = &["spawn", "run"]))]
#[strum_discriminants(strum(message = "Supports spawning a process"))]
ProcSpawn {
/// The full command to run including arguments
#[cfg_attr(feature = "clap", clap(flatten))]
cmd: Cmd,
/// Environment to provide to the remote process
#[serde(default)]
#[cfg_attr(feature = "clap", clap(long, default_value_t = Environment::default()))]
environment: Environment,
/// Alternative current directory for the remote process
#[serde(default)]
#[cfg_attr(feature = "clap", clap(long))]
current_dir: Option<PathBuf>,
/// If provided, will spawn process in a pty, otherwise spawns directly
#[serde(default)]
#[cfg_attr(feature = "clap", clap(long))]
pty: Option<PtySize>,
},
/// Kills a process running on the remote machine
#[cfg_attr(feature = "clap", clap(visible_aliases = &["kill"]))]
#[strum_discriminants(strum(message = "Supports killing a spawned process"))]
ProcKill {
/// Id of the actively-running process

@ -10,7 +10,7 @@ use std::{
path::PathBuf,
str::FromStr,
};
use strum::{EnumString, EnumVariantNames};
use strum::{EnumString, EnumVariantNames, VariantNames};
/// Change to one or more paths on the filesystem
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -58,8 +58,6 @@ impl From<NotifyEvent> for Change {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "clap", clap(rename_all = "snake_case"))]
pub enum ChangeKind {
/// Something about a file or directory was accessed, but
/// no specific details were known
@ -142,6 +140,16 @@ pub enum ChangeKind {
}
impl ChangeKind {
/// Returns a list of all variants as str names
pub const fn variants() -> &'static [&'static str] {
Self::VARIANTS
}
/// Returns a list of all variants as a vec
pub fn all() -> Vec<ChangeKind> {
ChangeKindSet::all().into_sorted_vec()
}
/// Returns true if the change is a kind of access
pub fn is_access_kind(&self) -> bool {
self.is_open_access_kind()
@ -375,9 +383,11 @@ impl ChangeKindSet {
ChangeKind::Rename | ChangeKind::RenameBoth | ChangeKind::RenameFrom | ChangeKind::RenameTo
}
/// Consumes set and returns a vec of the kinds of changes
pub fn into_vec(self) -> Vec<ChangeKind> {
self.0.into_iter().collect()
/// Consumes set and returns a sorted vec of the kinds of changes
pub fn into_sorted_vec(self) -> Vec<ChangeKind> {
let mut v = self.0.into_iter().collect::<Vec<_>>();
v.sort();
v
}
}

@ -302,17 +302,32 @@ impl UntypedClient {
Ok(response) => {
if log_enabled!(Level::Trace) {
trace!(
"Client receiving {}",
String::from_utf8_lossy(&response.to_bytes())
.to_string()
"Client receiving (id:{} | origin: {}): {}",
response.id,
response.origin_id,
String::from_utf8_lossy(&response.payload).to_string()
);
}
// For trace-level logging, we need to clone the id and
// origin id before passing the response ownership to
// be delivered elsewhere
let (id, origin_id) = if log_enabled!(Level::Trace) {
(response.id.to_string(), response.origin_id.to_string())
} else {
(String::new(), String::new())
};
// Try to send response to appropriate mailbox
// TODO: This will block if full... is that a problem?
// TODO: How should we handle false response? Did logging in past
post_office
if post_office
.deliver_untyped_response(response.into_owned())
.await;
.await
{
trace!("Client delivered response {id} to {origin_id}");
} else {
trace!("Client dropped response {id} to {origin_id}");
}
}
Err(x) => {
error!("Invalid response: {x}");

@ -5,10 +5,10 @@ use crate::constants::MAX_PIPE_CHUNK_SIZE;
use crate::options::{ClientFileSystemSubcommand, ClientSubcommand, Format, NetworkSettings};
use crate::{CliError, CliResult};
use anyhow::Context;
use distant_core::data::{FileType, SearchQuery, SystemInfo};
use distant_core::data::{ChangeKindSet, FileType, SearchQuery, SystemInfo};
use distant_core::net::common::{ConnectionId, Host, Map, Request, Response};
use distant_core::net::manager::ManagerClient;
use distant_core::{DistantChannel, DistantChannelExt};
use distant_core::{DistantChannel, DistantChannelExt, Watcher};
use distant_core::{DistantMsg, DistantRequestData, DistantResponseData, RemoteCommand, Searcher};
use log::*;
use serde_json::json;
@ -582,6 +582,47 @@ async fn async_run(cmd: ClientSubcommand) -> CliResult {
format!("Failed to copy {src:?} to {dst:?} using connection {connection_id}")
})?;
}
ClientSubcommand::FileSystem(ClientFileSystemSubcommand::Exists {
cache,
connection,
network,
path,
}) => {
debug!("Connecting to manager");
let mut client = Client::new(network)
.using_prompt_auth_handler()
.connect()
.await
.context("Failed to connect to manager")?;
let mut cache = read_cache(&cache).await;
let connection_id =
use_or_lookup_connection_id(&mut cache, connection, &mut client).await?;
debug!("Opening channel to connection {}", connection_id);
let channel = client
.open_raw_channel(connection_id)
.await
.with_context(|| format!("Failed to open channel to connection {connection_id}"))?;
debug!("Checking existence of {path:?}");
let exists = channel
.into_client()
.into_channel()
.exists(path.as_path())
.await
.with_context(|| {
format!(
"Failed to check existence of {path:?} using connection {connection_id}"
)
})?;
if exists {
println!("true");
} else {
println!("false");
}
}
ClientSubcommand::FileSystem(ClientFileSystemSubcommand::MakeDir {
cache,
connection,
@ -616,6 +657,135 @@ async fn async_run(cmd: ClientSubcommand) -> CliResult {
format!("Failed to make directory {path:?} using connection {connection_id}")
})?;
}
ClientSubcommand::FileSystem(ClientFileSystemSubcommand::Metadata {
cache,
connection,
network,
canonicalize,
resolve_file_type,
path,
}) => {
debug!("Connecting to manager");
let mut client = Client::new(network)
.using_prompt_auth_handler()
.connect()
.await
.context("Failed to connect to manager")?;
let mut cache = read_cache(&cache).await;
let connection_id =
use_or_lookup_connection_id(&mut cache, connection, &mut client).await?;
debug!("Opening channel to connection {}", connection_id);
let channel = client
.open_raw_channel(connection_id)
.await
.with_context(|| format!("Failed to open channel to connection {connection_id}"))?;
debug!("Retrieving metadata of {path:?}");
let metadata = channel
.into_client()
.into_channel()
.metadata(path.as_path(), canonicalize, resolve_file_type)
.await
.with_context(|| {
format!(
"Failed to retrieve metadata of {path:?} using connection {connection_id}"
)
})?;
println!(
concat!(
"{}",
"Type: {}\n",
"Len: {}\n",
"Readonly: {}\n",
"Created: {}\n",
"Last Accessed: {}\n",
"Last Modified: {}\n",
"{}",
"{}",
"{}",
),
metadata
.canonicalized_path
.map(|p| format!("Canonicalized Path: {p:?}\n"))
.unwrap_or_default(),
metadata.file_type.as_ref(),
metadata.len,
metadata.readonly,
metadata.created.unwrap_or_default(),
metadata.accessed.unwrap_or_default(),
metadata.modified.unwrap_or_default(),
metadata
.unix
.map(|u| format!(
concat!(
"Owner Read: {}\n",
"Owner Write: {}\n",
"Owner Exec: {}\n",
"Group Read: {}\n",
"Group Write: {}\n",
"Group Exec: {}\n",
"Other Read: {}\n",
"Other Write: {}\n",
"Other Exec: {}",
),
u.owner_read,
u.owner_write,
u.owner_exec,
u.group_read,
u.group_write,
u.group_exec,
u.other_read,
u.other_write,
u.other_exec
))
.unwrap_or_default(),
metadata
.windows
.map(|w| format!(
concat!(
"Archive: {}\n",
"Compressed: {}\n",
"Encrypted: {}\n",
"Hidden: {}\n",
"Integrity Stream: {}\n",
"Normal: {}\n",
"Not Content Indexed: {}\n",
"No Scrub Data: {}\n",
"Offline: {}\n",
"Recall on Data Access: {}\n",
"Recall on Open: {}\n",
"Reparse Point: {}\n",
"Sparse File: {}\n",
"System: {}\n",
"Temporary: {}",
),
w.archive,
w.compressed,
w.encrypted,
w.hidden,
w.integrity_stream,
w.normal,
w.not_content_indexed,
w.no_scrub_data,
w.offline,
w.recall_on_data_access,
w.recall_on_open,
w.reparse_point,
w.sparse_file,
w.system,
w.temporary,
))
.unwrap_or_default(),
if metadata.unix.is_none() && metadata.windows.is_none() {
String::from("\n")
} else {
String::new()
}
)
}
ClientSubcommand::FileSystem(ClientFileSystemSubcommand::Read {
cache,
connection,
@ -839,6 +1009,55 @@ async fn async_run(cmd: ClientSubcommand) -> CliResult {
formatter.print(res).context("Failed to print match")?;
}
}
ClientSubcommand::FileSystem(ClientFileSystemSubcommand::Watch {
cache,
connection,
network,
recursive,
only,
except,
path,
}) => {
debug!("Connecting to manager");
let mut client = Client::new(network)
.using_prompt_auth_handler()
.connect()
.await
.context("Failed to connect to manager")?;
let mut cache = read_cache(&cache).await;
let connection_id =
use_or_lookup_connection_id(&mut cache, connection, &mut client).await?;
debug!("Opening channel to connection {}", connection_id);
let channel = client
.open_raw_channel(connection_id)
.await
.with_context(|| format!("Failed to open channel to connection {connection_id}"))?;
debug!("Special request creating watcher for {:?}", path);
let mut watcher = Watcher::watch(
channel.into_client().into_channel(),
path.as_path(),
recursive,
only.into_iter().collect::<ChangeKindSet>(),
except.into_iter().collect::<ChangeKindSet>(),
)
.await
.with_context(|| format!("Failed to watch {path:?}"))?;
// Continue to receive and process changes
let mut formatter = Formatter::shell();
while let Some(change) = watcher.next().await {
// TODO: Provide a cleaner way to print just a change
let res = Response::new(
"".to_string(),
DistantMsg::Single(DistantResponseData::Changed(change)),
);
formatter.print(res).context("Failed to print change")?;
}
}
ClientSubcommand::FileSystem(ClientFileSystemSubcommand::Write {
cache,
connection,

@ -1,9 +1,10 @@
use crate::constants;
use crate::constants::user::CACHE_FILE_PATH_STR;
use clap::builder::TypedValueParser as _;
use clap::{Parser, Subcommand, ValueEnum, ValueHint};
use clap_complete::Shell as ClapCompleteShell;
use derive_more::IsVariant;
use distant_core::data::Environment;
use distant_core::data::{ChangeKind, Environment};
use distant_core::net::common::{ConnectionId, Destination, Map, PortRange};
use distant_core::net::server::Shutdown;
use service_manager::ServiceManagerKind;
@ -104,11 +105,14 @@ impl Options {
}
ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Copy { network, .. }
| ClientFileSystemSubcommand::Exists { network, .. }
| ClientFileSystemSubcommand::MakeDir { network, .. }
| ClientFileSystemSubcommand::Metadata { network, .. }
| ClientFileSystemSubcommand::Read { network, .. }
| ClientFileSystemSubcommand::Remove { network, .. }
| ClientFileSystemSubcommand::Rename { network, .. }
| ClientFileSystemSubcommand::Search { network, .. }
| ClientFileSystemSubcommand::Watch { network, .. }
| ClientFileSystemSubcommand::Write { network, .. },
) => {
network.merge(config.client.network);
@ -510,6 +514,28 @@ pub enum ClientFileSystemSubcommand {
dst: PathBuf,
},
/// Checks whether the specified path exists on the remote machine
Exists {
/// Location to store cached data
#[clap(
long,
value_hint = ValueHint::FilePath,
value_parser,
default_value = CACHE_FILE_PATH_STR.as_str()
)]
cache: PathBuf,
/// Specify a connection being managed
#[clap(long)]
connection: Option<ConnectionId>,
#[clap(flatten)]
network: NetworkSettings,
/// The path to the file or directory on the remote machine
path: PathBuf,
},
/// Creates a directory on the remote machine
MakeDir {
/// Location to store cached data
@ -536,6 +562,38 @@ pub enum ClientFileSystemSubcommand {
path: PathBuf,
},
/// Retrieves metadata for the specified path on the remote machine
Metadata {
/// Location to store cached data
#[clap(
long,
value_hint = ValueHint::FilePath,
value_parser,
default_value = CACHE_FILE_PATH_STR.as_str()
)]
cache: PathBuf,
/// Specify a connection being managed
#[clap(long)]
connection: Option<ConnectionId>,
#[clap(flatten)]
network: NetworkSettings,
/// Whether or not to include a canonicalized version of the path, meaning
/// returning the canonical, absolute form of a path with all
/// intermediate components normalized and symbolic links resolved
#[clap(long)]
canonicalize: bool,
/// Whether or not to follow symlinks to determine absolute file type (dir/file)
#[clap(long)]
resolve_file_type: bool,
/// The path to the file, directory, or symlink on the remote machine
path: PathBuf,
},
/// Reads the contents of a file or retrieves the entries within a directory on the remote
/// machine
Read {
@ -680,6 +738,53 @@ pub enum ClientFileSystemSubcommand {
paths: Vec<PathBuf>,
},
/// Watch a path for changes on the remote machine
Watch {
/// Location to store cached data
#[clap(
long,
value_hint = ValueHint::FilePath,
value_parser,
default_value = CACHE_FILE_PATH_STR.as_str()
)]
cache: PathBuf,
/// Specify a connection being managed
#[clap(long)]
connection: Option<ConnectionId>,
#[clap(flatten)]
network: NetworkSettings,
/// If true, will recursively watch for changes within directories, othewise
/// will only watch for changes immediately within directories
#[clap(long)]
recursive: bool,
/// Filter to only report back specified changes
#[
clap(
long,
value_parser = clap::builder::PossibleValuesParser::new(ChangeKind::variants())
.map(|s| s.parse::<ChangeKind>().unwrap()),
)
]
only: Vec<ChangeKind>,
/// Filter to report back changes except these specified changes
#[
clap(
long,
value_parser = clap::builder::PossibleValuesParser::new(ChangeKind::variants())
.map(|s| s.parse::<ChangeKind>().unwrap()),
)
]
except: Vec<ChangeKind>,
/// The path to the file, directory, or symlink on the remote machine
path: PathBuf,
},
/// Writes the contents to a file on the remote machine
Write {
/// Location to store cached data
@ -714,11 +819,14 @@ impl ClientFileSystemSubcommand {
pub fn cache_path(&self) -> &Path {
match self {
Self::Copy { cache, .. } => cache.as_path(),
Self::Exists { cache, .. } => cache.as_path(),
Self::MakeDir { cache, .. } => cache.as_path(),
Self::Metadata { cache, .. } => cache.as_path(),
Self::Read { cache, .. } => cache.as_path(),
Self::Remove { cache, .. } => cache.as_path(),
Self::Rename { cache, .. } => cache.as_path(),
Self::Search { cache, .. } => cache.as_path(),
Self::Watch { cache, .. } => cache.as_path(),
Self::Write { cache, .. } => cache.as_path(),
}
}
@ -726,11 +834,14 @@ impl ClientFileSystemSubcommand {
pub fn network_settings(&self) -> &NetworkSettings {
match self {
Self::Copy { network, .. } => network,
Self::Exists { network, .. } => network,
Self::MakeDir { network, .. } => network,
Self::Metadata { network, .. } => network,
Self::Read { network, .. } => network,
Self::Remove { network, .. } => network,
Self::Rename { network, .. } => network,
Self::Search { network, .. } => network,
Self::Watch { network, .. } => network,
Self::Write { network, .. } => network,
}
}
@ -1976,6 +2087,124 @@ mod tests {
);
}
#[test]
fn distant_fs_exists_should_support_merging_with_config() {
let mut options = Options {
config_path: None,
logging: LoggingSettings {
log_file: None,
log_level: None,
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Exists {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: None,
windows_pipe: None,
},
path: PathBuf::from("path"),
},
)),
};
options.merge(Config {
client: ClientConfig {
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
..Default::default()
},
..Default::default()
});
assert_eq!(
options,
Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Exists {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
path: PathBuf::from("path"),
}
)),
}
);
}
#[test]
fn distant_fs_exists_should_prioritize_explicit_cli_options_when_merging() {
let mut options = Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("cli-log-file")),
log_level: Some(LogLevel::Info),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Exists {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("cli-unix-socket")),
windows_pipe: Some(String::from("cli-windows-pipe")),
},
path: PathBuf::from("path"),
},
)),
};
options.merge(Config {
client: ClientConfig {
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
..Default::default()
},
..Default::default()
});
assert_eq!(
options,
Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("cli-log-file")),
log_level: Some(LogLevel::Info),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Exists {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("cli-unix-socket")),
windows_pipe: Some(String::from("cli-windows-pipe")),
},
path: PathBuf::from("path"),
}
)),
}
);
}
#[test]
fn distant_fs_makedir_should_support_merging_with_config() {
let mut options = Options {
@ -2098,6 +2327,132 @@ mod tests {
);
}
#[test]
fn distant_fs_metadata_should_support_merging_with_config() {
let mut options = Options {
config_path: None,
logging: LoggingSettings {
log_file: None,
log_level: None,
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Metadata {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: None,
windows_pipe: None,
},
canonicalize: true,
resolve_file_type: true,
path: PathBuf::from("path"),
},
)),
};
options.merge(Config {
client: ClientConfig {
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
..Default::default()
},
..Default::default()
});
assert_eq!(
options,
Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Metadata {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
canonicalize: true,
resolve_file_type: true,
path: PathBuf::from("path"),
}
)),
}
);
}
#[test]
fn distant_fs_metadata_should_prioritize_explicit_cli_options_when_merging() {
let mut options = Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("cli-log-file")),
log_level: Some(LogLevel::Info),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Metadata {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("cli-unix-socket")),
windows_pipe: Some(String::from("cli-windows-pipe")),
},
canonicalize: true,
resolve_file_type: true,
path: PathBuf::from("path"),
},
)),
};
options.merge(Config {
client: ClientConfig {
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
..Default::default()
},
..Default::default()
});
assert_eq!(
options,
Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("cli-log-file")),
log_level: Some(LogLevel::Info),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Metadata {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("cli-unix-socket")),
windows_pipe: Some(String::from("cli-windows-pipe")),
},
canonicalize: true,
resolve_file_type: true,
path: PathBuf::from("path"),
}
)),
}
);
}
#[test]
fn distant_fs_read_should_support_merging_with_config() {
let mut options = Options {
@ -2606,6 +2961,136 @@ mod tests {
);
}
#[test]
fn distant_fs_watch_should_support_merging_with_config() {
let mut options = Options {
config_path: None,
logging: LoggingSettings {
log_file: None,
log_level: None,
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Watch {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: None,
windows_pipe: None,
},
recursive: true,
only: ChangeKind::all(),
except: ChangeKind::all(),
path: PathBuf::from("path"),
},
)),
};
options.merge(Config {
client: ClientConfig {
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
..Default::default()
},
..Default::default()
});
assert_eq!(
options,
Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Watch {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
recursive: true,
only: ChangeKind::all(),
except: ChangeKind::all(),
path: PathBuf::from("path"),
}
)),
}
);
}
#[test]
fn distant_fs_watch_should_prioritize_explicit_cli_options_when_merging() {
let mut options = Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("cli-log-file")),
log_level: Some(LogLevel::Info),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Watch {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("cli-unix-socket")),
windows_pipe: Some(String::from("cli-windows-pipe")),
},
recursive: true,
only: ChangeKind::all(),
except: ChangeKind::all(),
path: PathBuf::from("path"),
},
)),
};
options.merge(Config {
client: ClientConfig {
logging: LoggingSettings {
log_file: Some(PathBuf::from("config-log-file")),
log_level: Some(LogLevel::Trace),
},
network: NetworkSettings {
unix_socket: Some(PathBuf::from("config-unix-socket")),
windows_pipe: Some(String::from("config-windows-pipe")),
},
..Default::default()
},
..Default::default()
});
assert_eq!(
options,
Options {
config_path: None,
logging: LoggingSettings {
log_file: Some(PathBuf::from("cli-log-file")),
log_level: Some(LogLevel::Info),
},
command: DistantSubcommand::Client(ClientSubcommand::FileSystem(
ClientFileSystemSubcommand::Watch {
cache: PathBuf::new(),
connection: None,
network: NetworkSettings {
unix_socket: Some(PathBuf::from("cli-unix-socket")),
windows_pipe: Some(String::from("cli-windows-pipe")),
},
recursive: true,
only: ChangeKind::all(),
except: ChangeKind::all(),
path: PathBuf::from("path"),
}
)),
}
);
}
#[test]
fn distant_fs_write_should_support_merging_with_config() {
let mut options = Options {

@ -1,7 +1,7 @@
use crate::cli::{fixtures::*, utils::ThreadedReader};
use assert_fs::prelude::*;
use rstest::*;
use std::{process::Command, thread, time::Duration};
use std::{thread, time::Duration};
fn wait_a_bit() {
wait_millis(250);
@ -17,14 +17,15 @@ fn wait_millis(millis: u64) {
#[rstest]
#[test_log::test]
fn should_support_watching_a_single_file(mut action_std_cmd: CtxCommand<Command>) {
fn should_support_watching_a_single_file(ctx: DistantManagerCtx) {
let temp = assert_fs::TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
// distant action watch {path}
let mut child = action_std_cmd
.args(["watch", file.to_str().unwrap()])
// distant fs watch {path}
let mut child = ctx
.new_std_cmd(["fs", "watch"])
.arg(file.to_str().unwrap())
.spawn()
.expect("Failed to execute");
@ -68,7 +69,7 @@ fn should_support_watching_a_single_file(mut action_std_cmd: CtxCommand<Command>
#[rstest]
#[test_log::test]
fn should_support_watching_a_directory_recursively(mut action_std_cmd: CtxCommand<Command>) {
fn should_support_watching_a_directory_recursively(ctx: DistantManagerCtx) {
let temp = assert_fs::TempDir::new().unwrap();
let dir = temp.child("dir");
@ -77,9 +78,10 @@ fn should_support_watching_a_directory_recursively(mut action_std_cmd: CtxComman
let file = dir.child("file");
file.touch().unwrap();
// distant action watch {path}
let mut child = action_std_cmd
.args(["watch", "--recursive", temp.to_str().unwrap()])
// distant fs watch {path}
let mut child = ctx
.new_std_cmd(["fs", "watch"])
.args(["--recursive", temp.to_str().unwrap()])
.spawn()
.expect("Failed to execute");
@ -123,13 +125,14 @@ fn should_support_watching_a_directory_recursively(mut action_std_cmd: CtxComman
#[rstest]
#[test_log::test]
fn yield_an_error_when_fails(mut action_std_cmd: CtxCommand<Command>) {
fn yield_an_error_when_fails(ctx: DistantManagerCtx) {
let temp = assert_fs::TempDir::new().unwrap();
let invalid_path = temp.to_path_buf().join("missing");
// distant action watch {path}
let child = action_std_cmd
.args(["watch", invalid_path.to_str().unwrap()])
// distant fs watch {path}
let child = ctx
.new_std_cmd(["fs", "watch"])
.arg(invalid_path.to_str().unwrap())
.spawn()
.expect("Failed to execute");

@ -260,6 +260,7 @@ impl DistantManagerCtx {
cmd.arg("--unix-socket").arg(self.socket_or_pipe.as_str());
}
eprintln!("new_assert_cmd: {cmd:?}");
cmd
}
@ -287,6 +288,7 @@ impl DistantManagerCtx {
.stdout(Stdio::piped())
.stderr(Stdio::piped());
eprintln!("new_std_cmd: {cmd:?}");
cmd
}
}

Loading…
Cancel
Save