diff --git a/Cargo.lock b/Cargo.lock index 3657aaba..a88b0b4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,27 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "assert_cmd" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -269,6 +290,17 @@ dependencies = [ "log", ] +[[package]] +name = "bstr" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bufstream-fresh" version = "0.3.1" @@ -516,6 +548,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "dirs-next" version = "2.0.0" @@ -537,6 +575,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" version = "1.9.0" @@ -698,6 +742,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1251,6 +1304,7 @@ dependencies = [ name = "meli" version = "0.8.6" dependencies = [ + "assert_cmd", "async-task", "bitflags 2.4.0", "crossbeam", @@ -1264,6 +1318,7 @@ dependencies = [ "nix", "notify-rust", "pcre2", + "predicates", "proc-macro2", "quote", "regex", @@ -1410,6 +1465,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "notify" version = "6.1.1" @@ -1634,6 +1695,36 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2176,6 +2267,12 @@ dependencies = [ "redox_termios", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "ternop" version = "1.0.1" @@ -2416,6 +2513,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "waker-fn" version = "1.1.0" diff --git a/meli/Cargo.toml b/meli/Cargo.toml index 0fe1b089..81cf7a8e 100644 --- a/meli/Cargo.toml +++ b/meli/Cargo.toml @@ -83,7 +83,9 @@ regex = "1" syn = { version = "1", features = [] } [dev-dependencies] +assert_cmd = { version = "2" } flate2 = { version = "1" } +predicates = { version = "3" } regex = "1" tempfile = "3.3" diff --git a/meli/tests/test_cli_subcommands.rs b/meli/tests/test_cli_subcommands.rs new file mode 100644 index 00000000..f9d576ca --- /dev/null +++ b/meli/tests/test_cli_subcommands.rs @@ -0,0 +1,188 @@ +// +// meli +// +// Copyright 2024 Emmanouil Pitsidianakis +// +// This file is part of meli. +// +// meli is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// meli is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with meli. If not, see . +// +// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later + +use std::{io::Write, path::Path}; + +use assert_cmd::{assert::OutputAssertExt, Command}; +use predicates::prelude::*; +use tempfile::TempDir; + +#[test] +fn test_cli_subcommands() { + for var in [ + "PAGER", + "MANPATH", + "EDITOR", + "MELI_CONFIG", + "HOME", + "XDG_CACHE_HOME", + "XDG_STATE_HOME", + "XDG_CONFIG_DIRS", + "XDG_CONFIG_HOME", + "XDG_DATA_DIRS", + "XDG_DATA_HOME", + ] { + std::env::remove_var(var); + } + + fn version() { + // --version is successful + for arg in ["--version", "-v"] { + let mut cmd = Command::cargo_bin("meli").unwrap(); + let output = cmd.arg(arg).output().unwrap().assert(); + output.code(0).stdout(predicates::str::starts_with("meli ")); + } + } + + fn help() { + // --help is successful + for arg in ["--help", "-h"] { + let mut cmd = Command::cargo_bin("meli").unwrap(); + let output = cmd.arg(arg).output().unwrap().assert(); + output + .code(0) + .stdout(predicates::str::contains("terminal mail client")) + .stdout(predicates::str::contains("USAGE")) + .stdout(predicates::str::contains("FLAGS")) + .stdout(predicates::str::contains("OPTIONS")) + .stdout(predicates::str::contains("SUBCOMMANDS")) + .stdout(predicates::str::contains("create-config")) + .stdout(predicates::str::contains("test-config")) + .stdout(predicates::str::contains("tools")) + .stdout(predicates::str::contains("man")) + .stdout(predicates::str::contains("install-man")) + .stdout(predicates::str::contains("compiled-with")) + .stdout(predicates::str::contains("edit-config")) + .stdout(predicates::str::contains("help")) + .stdout(predicates::str::contains("print-app-directories")) + .stdout(predicates::str::contains("print-config-path")) + .stdout(predicates::str::contains("print-default-theme")) + .stdout(predicates::str::contains("print-loaded-themes")) + .stdout(predicates::str::contains("print-log-path")) + .stdout(predicates::str::contains("view")); + } + } + + fn test_subcommand_succeeds(arg: &str) { + let mut cmd = Command::cargo_bin("meli").unwrap(); + let output = cmd.arg(arg).output().unwrap().assert(); + output.code(0).stdout(predicates::str::is_empty().not()); + } + + fn test_subcommand_succeeds_empty(arg: &str) { + let mut cmd = Command::cargo_bin("meli").unwrap(); + let output = cmd.arg(arg).output().unwrap().assert(); + output.code(0).stdout(predicates::str::is_empty()); + } + + fn test_subcommand_install_man(dir: &Path) { + let mut cmd = Command::cargo_bin("meli").unwrap(); + let output = cmd.arg("install-man").arg(dir).output().unwrap().assert(); + output.code(0).stdout(predicates::str::is_empty().not()); + let mut path = dir.to_path_buf(); + for (man, dir) in [ + ("meli.1", "man1"), + ("meli.conf.5", "man5"), + ("meli-themes.5", "man5"), + ("meli.7", "man7"), + ] { + path.push(dir); + assert!(path.is_dir()); + path.push(man); + assert!(path.is_file()); + path.pop(); + path.pop(); + } + } + + version(); + help(); + test_subcommand_succeeds("help"); + test_subcommand_succeeds("compiled-with"); + test_subcommand_succeeds("man"); + + let tmp_dir = TempDir::new().unwrap(); + + test_subcommand_install_man(tmp_dir.path()); + + fn config_not_exists(conf: &Path) { + let mut cmd = Command::cargo_bin("meli").unwrap(); + let output = cmd.arg("-c").arg(conf).output().unwrap().assert(); + output + .code(1) + .stderr(predicate::eq( + "Edit the sample configuration and relaunch meli.\nKind: Configuration\n", + )) + .stdout( + predicate::eq( + format!( + "No configuration found. Would you like to generate one in {path}? [Y/n] \ + Written example configuration to {path}", + path = conf.display() + ) + .as_str(), + ) + .trim() + .normalize(), + ); + } + + let conf_path = tmp_dir.path().join("conf.toml"); + config_not_exists(&conf_path); + assert!(conf_path.exists()); + assert!(conf_path.is_file()); + + { + let mut conf_file = std::fs::OpenOptions::new() + .append(true) + .create(false) + .open(&conf_path) + .unwrap(); + conf_file + .write_all( + br#" +[accounts.imap] +root_mailbox = "INBOX" +format = "imap" +identity="username@example.com" +server_username = "null" +server_hostname = "example.com" +server_password_command = "false" + +[composing] +send_mail = 'false' + "#, + ) + .unwrap(); + } + std::env::set_var("MELI_CONFIG", &conf_path); + + test_subcommand_succeeds_empty("test-config"); + + test_subcommand_succeeds("print-app-directories"); + test_subcommand_succeeds("print-config-path"); + test_subcommand_succeeds("print-default-theme"); + test_subcommand_succeeds("print-loaded-themes"); + test_subcommand_succeeds("print-log-path"); + + tmp_dir.close().unwrap(); +}