diff --git a/Cargo.lock b/Cargo.lock index 4ee81e6..b62b95a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,20 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "assert_fs" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0ca6aa3066e6c6f0357e056a25fa95e8737f15a04f9aead0b22d0d082a39465" +dependencies = [ + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", +] + [[package]] name = "atty" version = "0.2.14" @@ -144,6 +158,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "crypto-bigint" version = "0.2.4" @@ -213,6 +237,7 @@ name = "distant" version = "0.13.0" dependencies = [ "assert_cmd", + "assert_fs", "derive_more", "distant-core", "flexi_logger", @@ -220,10 +245,10 @@ dependencies = [ "lazy_static", "log", "rand", + "rstest", "serde_json", "structopt", "strum", - "tempfile", "tokio", "whoami", ] @@ -318,6 +343,12 @@ dependencies = [ "yansi", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fork" version = "0.1.18" @@ -448,6 +479,30 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "group" version = "0.10.0" @@ -499,6 +554,24 @@ dependencies = [ "digest", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "instant" version = "0.1.10" @@ -879,6 +952,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "rstest" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2288c66aeafe3b2ed227c981f364f9968fa952ef0b30e84ada4486e7ee24d00a" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.5" @@ -900,6 +995,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + [[package]] name = "serde" version = "1.0.130" @@ -1105,6 +1206,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.43" diff --git a/Cargo.toml b/Cargo.toml index 1ab7274..aa0114f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,4 +35,5 @@ whoami = "1.1.2" [dev-dependencies] assert_cmd = "2.0.0" -tempfile = "3.2.0" +assert_fs = "1.0.3" +rstest = "0.11.0" diff --git a/src/lib.rs b/src/lib.rs index cdb7308..6e491ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,23 +26,28 @@ pub fn run() { fn init_logging(opt: &opt::CommonOpt) -> flexi_logger::LoggerHandle { use flexi_logger::{FileSpec, LevelFilter, LogSpecification, Logger}; - let module = "distant"; + let modules = &["distant", "distant_core"]; // Disable logging for everything but our binary, which is based on verbosity let mut builder = LogSpecification::builder(); - builder.default(LevelFilter::Off).module( - module, - match opt.verbose { - 0 => LevelFilter::Warn, - 1 => LevelFilter::Info, - 2 => LevelFilter::Debug, - _ => LevelFilter::Trace, - }, - ); - - // If quiet, we suppress all output - if opt.quiet { - builder.module(module, LevelFilter::Off); + builder.default(LevelFilter::Off); + + // For each module, configure logging + for module in modules { + builder.module( + module, + match opt.verbose { + 0 => LevelFilter::Warn, + 1 => LevelFilter::Info, + 2 => LevelFilter::Debug, + _ => LevelFilter::Trace, + }, + ); + + // If quiet, we suppress all output + if opt.quiet { + builder.module(module, LevelFilter::Off); + } } // Create our logger, but don't initialize yet diff --git a/tests/action/file_read_test.rs b/tests/action/file_read_test.rs new file mode 100644 index 0000000..85febdf --- /dev/null +++ b/tests/action/file_read_test.rs @@ -0,0 +1,77 @@ +use crate::fixtures::*; +use assert_fs::prelude::*; +use distant_core::{ + data::{Error, ErrorKind}, + Response, ResponseData, +}; +use rstest::*; + +#[rstest] +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn should_print_out_file_contents(#[future] ctx: DistantServerCtx) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("test-file"); + file.write_str("some\ntext\ncontent").unwrap(); + + ctx.await + .new_cmd("action") + .args(&["file-read", file.to_str().unwrap()]) + .assert() + .success() + .stdout("some\ntext\ncontent\n") + .stderr(""); +} + +#[rstest] +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn should_support_json_output(#[future] ctx: DistantServerCtx) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("test-file"); + file.write_str("some\ntext\ncontent").unwrap(); + + let cmd = ctx + .await + .new_cmd("action") + .args(&["--format", "json"]) + .args(&["file-read", file.to_str().unwrap()]) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!( + res.payload[0], + ResponseData::Blob { + data: b"some\ntext\ncontent".to_vec() + } + ); +} + +#[rstest] +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn yield_an_error_when_fails(#[future] ctx: DistantServerCtx) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("missing-file"); + + let cmd = ctx + .await + .new_cmd("action") + .args(&["--format", "json"]) + .args(&["file-read", file.to_str().unwrap()]) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert!( + matches!( + res.payload[0], + ResponseData::Error(Error { + kind: ErrorKind::NotFound, + .. + }) + ), + "Unexpected response: {:?}", + res.payload[0] + ); +} diff --git a/tests/action/mod.rs b/tests/action/mod.rs new file mode 100644 index 0000000..4c187cf --- /dev/null +++ b/tests/action/mod.rs @@ -0,0 +1 @@ +mod file_read_test; diff --git a/tests/e2e_tests.rs b/tests/e2e_tests.rs new file mode 100644 index 0000000..68592be --- /dev/null +++ b/tests/e2e_tests.rs @@ -0,0 +1,2 @@ +mod action; +mod fixtures; diff --git a/tests/fixtures.rs b/tests/fixtures.rs new file mode 100644 index 0000000..a66e504 --- /dev/null +++ b/tests/fixtures.rs @@ -0,0 +1,56 @@ +use assert_cmd::Command; +use distant_core::*; +use rstest::*; +use std::{ffi::OsStr, net::SocketAddr, time::Duration}; + +/// Timeout to wait for a command to complete +const TIMEOUT_SECS: u64 = 10; + +/// Context for some listening distant server +pub struct DistantServerCtx { + pub addr: SocketAddr, + pub auth_key: String, + pub server: DistantServer, +} + +impl DistantServerCtx { + /// Produces a new test command that configures some distant command + /// configured with an environment that can talk to a remote distant server + pub fn new_cmd(&self, subcommand: impl AsRef) -> Command { + let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); + + println!("DISTANT_HOST = {}", self.addr.ip()); + println!("DISTANT_PORT = {}", self.addr.port()); + println!("DISTANT_AUTH_KEY = {}", self.auth_key); + + // NOTE: We define a command that has a timeout of 10s because the handshake + // involved in a non-release test can take several seconds + cmd.arg(subcommand) + .args(&["--session", "environment"]) + .env("DISTANT_HOST", self.addr.ip().to_string()) + .env("DISTANT_PORT", self.addr.port().to_string()) + .env("DISTANT_AUTH_KEY", self.auth_key.as_str()) + .timeout(Duration::from_secs(TIMEOUT_SECS)); + cmd + } +} + +impl Drop for DistantServerCtx { + fn drop(&mut self) { + self.server.abort(); + } +} + +#[fixture] +pub async fn ctx() -> DistantServerCtx { + let ip_addr = "127.0.0.1".parse().unwrap(); + let server = DistantServer::bind(ip_addr, "0".parse().unwrap(), None, 100) + .await + .unwrap(); + + DistantServerCtx { + addr: SocketAddr::new(ip_addr, server.port()), + auth_key: server.to_unprotected_hex_auth_key(), + server, + } +}