From b3deb65f36187b8c042e28d4a382fe816d6a5657 Mon Sep 17 00:00:00 2001 From: davidko Date: Thu, 9 Jan 2025 01:13:51 +0800 Subject: [PATCH] feat: lock and unlock commands --- Cargo.lock | 69 +++++++++++++++++- Cargo.toml | 2 + huber-common/src/model/config.rs | 30 +++++--- huber-procmacro/src/lib.rs | 6 +- huber/Cargo.toml | 2 + huber/src/bin/huber.rs | 11 +-- huber/src/cmd/current.rs | 12 ++- huber/src/cmd/install.rs | 24 +++--- huber/src/cmd/load.rs | 6 +- huber/src/cmd/lock.rs | 121 +++++++++++++++++++++---------- huber/src/cmd/repo.rs | 5 +- huber/src/cmd/save.rs | 13 +++- huber/src/cmd/search.rs | 3 +- huber/src/cmd/show.rs | 5 +- huber/src/cmd/uninstall.rs | 3 +- huber/src/cmd/unlock.rs | 66 ++++++++++++----- huber/src/cmd/update.rs | 85 ++++++++++++++++------ huber/src/error.rs | 26 ++++++- huber/src/opt.rs | 14 ++-- huber/src/service/cache.rs | 5 +- huber/src/service/package.rs | 7 +- huber/src/service/release.rs | 9 ++- huber/tests/common/mod.rs | 77 ++++++++++++++++++++ huber/tests/config.rs | 11 +++ huber/tests/current.rs | 57 +++++++++++++++ huber/tests/flush.rs | 56 ++++++++++++++ huber/tests/info.rs | 53 ++++++++++++++ huber/tests/install.rs | 40 ++++++++++ huber/tests/load.rs | 41 +++++++++++ huber/tests/lock.rs | 51 +++++++++++++ huber/tests/reset.rs | 10 +++ huber/tests/save.rs | 34 +++++++++ huber/tests/uninstall.rs | 43 +++++++++++ justfile | 6 +- 34 files changed, 858 insertions(+), 145 deletions(-) create mode 100644 huber/tests/current.rs create mode 100644 huber/tests/flush.rs create mode 100644 huber/tests/info.rs create mode 100644 huber/tests/install.rs create mode 100644 huber/tests/load.rs create mode 100644 huber/tests/lock.rs create mode 100644 huber/tests/reset.rs create mode 100644 huber/tests/save.rs create mode 100644 huber/tests/uninstall.rs diff --git a/Cargo.lock b/Cargo.lock index 6b492db9..918bc52c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -572,6 +582,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "filepath" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81dd23d67f94e07ac941a8da9bd0c2a82d3ea063b7d8a921d1dd05f9052a407" +dependencies = [ + "libc", + "windows", +] + [[package]] name = "fnv" version = "1.0.7" @@ -846,11 +866,13 @@ dependencies = [ "anyhow", "assert_cmd", "async-trait", + "better-panic", "chrono", "clap", "clap_complete", "compress-tools", "dirs", + "filepath", "fs2", "fs_extra", "futures", @@ -1035,7 +1057,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -2843,6 +2865,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -2852,6 +2884,41 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-registry" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 24c443ed..b3e32e50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,3 +64,5 @@ fs2 = "0.4.3" clap_complete = "4.5.40" thiserror = "2.0.9" scopeguard = "1.1.0" +better-panic = "0.3.0" +filepath = "0.2.0" diff --git a/huber-common/src/model/config.rs b/huber-common/src/model/config.rs index d4b2ae5c..2b025401 100644 --- a/huber-common/src/model/config.rs +++ b/huber-common/src/model/config.rs @@ -21,7 +21,7 @@ pub struct Config { pub github_token: Option, pub github_key: Option, pub github_base_uri: Option, - pub lock_pkg_versions: HashMap>, + pub lock_pkg_versions: HashMap, } impl Config { @@ -32,17 +32,27 @@ impl Config { github_token: Option, github_key: Option, github_base_uri: Option, - lock_pkg_versions: HashMap>, + lock_pkg_versions: HashMap, ) -> Self { - Self { - log_level, - output_format, - huber_dir: dir(huber_dir).unwrap(), - github_token, - github_key, - github_base_uri, - lock_pkg_versions, + let mut config = Config::default(); + + config.log_level = log_level; + config.output_format = output_format; + config.huber_dir = huber_dir; + if let Some(token) = github_token { + config.github_token = Some(token); + } + if let Some(key) = github_key { + config.github_key = Some(key); + } + if let Some(uri) = github_base_uri { + config.github_base_uri = Some(uri); } + if !lock_pkg_versions.is_empty() { + config.lock_pkg_versions = lock_pkg_versions; + } + + config } } diff --git a/huber-procmacro/src/lib.rs b/huber-procmacro/src/lib.rs index 52a44018..0c205fad 100644 --- a/huber-procmacro/src/lib.rs +++ b/huber-procmacro/src/lib.rs @@ -12,7 +12,7 @@ pub fn process_lock(input: TokenStream) -> TokenStream { use huber_common::model::config::Config; use std::fs::File; use fs2::FileExt; - use log::{error, info}; + use log::debug; use anyhow::anyhow; let lock_path = #lock_file_pathbuf_expr; @@ -25,11 +25,11 @@ pub fn process_lock(input: TokenStream) -> TokenStream { let r = f.try_lock_exclusive(); match r { Ok(_) => { - info!("{}: {:?}", "Locking the operation", lock_path); + debug!("{}: {:?}", "Locking the operation", lock_path); }, Err(e) => { - error!("{:?}: {:?}", lock_path, e); + debug!("{:?}: {:?}", lock_path, e); return Err(anyhow!("huber is already running by another process for the exclusion operation. Please try after the operation finished. {:?}", e)) } } diff --git a/huber/Cargo.toml b/huber/Cargo.toml index f27e1d15..aa2adfb9 100644 --- a/huber/Cargo.toml +++ b/huber/Cargo.toml @@ -26,11 +26,13 @@ rstest = "0.24.0" [dependencies] anyhow.workspace = true async-trait.workspace = true +better-panic.workspace = true chrono.workspace = true clap.workspace = true clap_complete.workspace = true compress-tools.workspace = true dirs.workspace = true +filepath.workspace = true fs2.workspace = true fs_extra.workspace = true futures.workspace = true diff --git a/huber/src/bin/huber.rs b/huber/src/bin/huber.rs index ad2e3eca..c56ba2e6 100644 --- a/huber/src/bin/huber.rs +++ b/huber/src/bin/huber.rs @@ -140,19 +140,14 @@ async fn main() { if let Some(e) = e.downcast_ref::() { error!("{}", e); } else if let Some(e) = e.downcast_ref::() { - match e { - HuberError::ConfigNotFound(_) => { - warn!( - "Config not found, please run `huber config save` to create a new one \ - if want to persist the configuration" - ); - } - } + warn!("{}", e); } } } fn init(cli: &Cli) -> (Config, Arc) { + better_panic::install(); + let config = Config::new( cli.log_level.to_string(), OutputFormat::from_str(&cli.output_format).unwrap(), diff --git a/huber/src/cmd/current.rs b/huber/src/cmd/current.rs index 6f3c5c15..62afb05a 100644 --- a/huber/src/cmd/current.rs +++ b/huber/src/cmd/current.rs @@ -6,7 +6,8 @@ use log::info; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; -use crate::opt::parse_pkg_name_version; +use crate::error::HuberError::PackageNotInstalled; +use crate::opt::parse_pkg_name_semver; use crate::service::package::PackageService; use crate::service::release::{ReleaseAsyncTrait, ReleaseService}; use crate::service::{ItemOperationAsyncTrait, ItemOperationTrait}; @@ -17,7 +18,8 @@ pub struct CurrentArgs { help = "Package name with version (e.g. 'package-name@version')", num_args = 1, value_hint = ValueHint::Unknown, - value_parser = parse_pkg_name_version + value_parser = parse_pkg_name_semver, + required = true, )] name_version: Vec<(String, String)>, } @@ -25,16 +27,12 @@ pub struct CurrentArgs { #[async_trait] impl CommandTrait for CurrentArgs { async fn run(&self, _: &Config, container: &DIContainer) -> anyhow::Result<()> { - if self.name_version.is_empty() { - return Err(anyhow!("No package name with version provided")); - } - let release_service = container.get::().unwrap(); let pkg_service = container.get::().unwrap(); for (name, version) in self.name_version.iter() { if !pkg_service.has(name)? { - return Err(anyhow!("{} not installed", name)); + return Err(anyhow!(PackageNotInstalled(name.clone()))); } let pkg = pkg_service.get(name)?; diff --git a/huber/src/cmd/install.rs b/huber/src/cmd/install.rs index 36f4e5af..ee29a521 100644 --- a/huber/src/cmd/install.rs +++ b/huber/src/cmd/install.rs @@ -9,6 +9,8 @@ use simpledi_rs::di::{DIContainer, DIContainerTrait}; use tokio::task::JoinHandle; use crate::cmd::CommandTrait; +use crate::error::HuberError::PackageNotFound; +use crate::opt::parse_pkg_name_semver; use crate::service::cache::{CacheAsyncTrait, CacheService}; use crate::service::package::PackageService; use crate::service::release::ReleaseService; @@ -19,14 +21,15 @@ pub struct InstallArgs { #[arg( help = "Package name (e.g. 'package-name' or 'package-name@version')", num_args = 1, - value_hint = ValueHint::Unknown + required = true, + value_parser = parse_pkg_name_semver, + value_hint = ValueHint::Unknown, )] - name_version: Vec, + name_version: Vec<(String, String)>, #[arg( help = "Set the installed package as current", long, - num_args = 1, value_hint = ValueHint::Unknown )] current: bool, @@ -41,8 +44,7 @@ impl CommandTrait for InstallArgs { let cache_service = container.get::().unwrap(); cache_service.update_repositories().await?; - let versions: Vec<_> = parse_package_name_versions(&self.name_version); - install_packages(release_service, pkg_service, versions).await?; + install_packages(release_service, pkg_service, &self.name_version).await?; Ok(()) } @@ -64,21 +66,21 @@ pub fn parse_package_name_versions(name_versions: &[String]) -> Vec<(String, Str pub async fn install_packages( release_service: Arc, pkg_service: Arc, - versions: Vec<(String, String)>, + pkg_versions: &Vec<(String, String)>, ) -> anyhow::Result<()> { - for (name, _) in versions.iter() { - if !pkg_service.has(name)? { - return Err(anyhow!("{} package not found", name)); + for (pkg, _) in pkg_versions.iter() { + if !pkg_service.has(pkg)? { + return Err(anyhow!(PackageNotFound(pkg.clone()))); } } let mut join_handles = vec![]; - for (name, version) in versions.clone().into_iter() { + for (pkg, version) in pkg_versions.clone().into_iter() { let pkg_service = pkg_service.clone(); let release_service = release_service.clone(); let handle: JoinHandle> = tokio::spawn(async move { - let mut pkg = pkg_service.get(&name)?; + let mut pkg = pkg_service.get(&pkg)?; pkg.version = if version.is_empty() { None } else { diff --git a/huber/src/cmd/load.rs b/huber/src/cmd/load.rs index 865abf9d..13d28d83 100644 --- a/huber/src/cmd/load.rs +++ b/huber/src/cmd/load.rs @@ -20,6 +20,7 @@ pub struct LoadArgs { help = "Load a package list to install", long, num_args = 1, + default_value = "huber-packages.txt", value_hint = ValueHint::Unknown )] file: String, @@ -35,6 +36,7 @@ impl CommandTrait for LoadArgs { cache_service.update_repositories().await?; info!("Loading packages from {}", self.file); + let file = File::open(&self.file)?; let reader = BufReader::new(file); let versions: Vec<_> = reader.lines().map_while(Result::ok).collect(); @@ -42,8 +44,10 @@ impl CommandTrait for LoadArgs { info!("Loaded packages: total {}: {:#?}", count, versions); info!("Installing packages: total {}", count); + let versions: Vec<_> = parse_package_name_versions(&versions); - install_packages(release_service, pkg_service, versions).await?; + install_packages(release_service, pkg_service, &versions).await?; + info!("Installed packages: total {}", count); Ok(()) diff --git a/huber/src/cmd/lock.rs b/huber/src/cmd/lock.rs index 5f511123..b0d97519 100644 --- a/huber/src/cmd/lock.rs +++ b/huber/src/cmd/lock.rs @@ -11,62 +11,61 @@ use serde::{Deserialize, Serialize}; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; -use crate::opt::parse_pkg_name_version; +use crate::error::HuberError::{ + NoPackagesLocked, PackageNotFound, PackageNotInstalled, PackageUnableToLock, +}; +use crate::opt::parse_pkg_name_semver; use crate::service::config::{ConfigService, ConfigTrait}; use crate::service::package::PackageService; +use crate::service::release::ReleaseService; use crate::service::ItemOperationTrait; #[derive(Args)] pub struct LockArgs { #[arg( - help = "Package name (e.g. 'package-name' or 'package-name@version')", + help = "Package name (e.g. 'package-name' or 'package-name@version', \ + the optional version follows Cargo's dependency version requirement format)", num_args = 1, value_hint = ValueHint::Unknown, - value_parser = parse_pkg_name_version + value_parser = parse_pkg_name_semver, )] name_version: Vec<(String, String)>, + + #[arg( + help = "Lock all installed `current` packages", + long, + conflicts_with = "name_version", + value_hint = ValueHint::Unknown + )] + all: bool, } #[async_trait] impl CommandTrait for LockArgs { async fn run(&self, config: &Config, container: &DIContainer) -> anyhow::Result<()> { - if self.name_version.is_empty() { - return display_pkg_configs(config); + if !self.all && self.name_version.is_empty() { + info!("No packages specified to lock. Showing locked packages instead"); + return display_locked_pkgs(config); } let pkg_service = container.get::().unwrap(); - let mut config = config.clone(); - let mut require_update = false; - - for (pkg, version) in &self.name_version { - if !pkg_service.has(pkg)? { - return Err(anyhow!("{} package not found", pkg)); - } - - info!("Locking package: {}@{}", pkg, version); - if let Some(versions) = config.lock_pkg_versions.get_mut(pkg) { - if !versions.contains(version) { - versions.push(version.clone()); - } else { - info!("Package {}@{} already locked", pkg, version); - } - } else { - config - .lock_pkg_versions - .insert(pkg.clone(), vec![version.clone()]); - } - - if !require_update { - require_update = true; - } - } + let release_service = container.get::().unwrap(); + let config_service = container.get::().unwrap(); + + info!("Locking packages"); - if !require_update { - info!("No packages to lock"); - return Ok(()); + let mut config = config.clone(); + if self.all { + lock_installed_current_pkgs(&mut config, release_service)?; + } else { + lock_pkgs( + &mut config, + pkg_service, + release_service, + &self.name_version, + )?; } - let config_service = container.get::().unwrap(); config_service.update(&config)?; info!("Packages locked successfully"); @@ -74,22 +73,68 @@ impl CommandTrait for LockArgs { } } -fn display_pkg_configs(config: &Config) -> anyhow::Result<()> { +fn lock_pkgs( + config: &mut Config, + pkg_service: &PackageService, + release_service: &ReleaseService, + name_versions: &Vec<(String, String)>, +) -> anyhow::Result<()> { + for (pkg, version) in name_versions { + if !pkg_service.has(pkg)? { + return Err(anyhow!(PackageNotFound(pkg.clone()))); + } + + if !release_service.has(pkg)? { + return Err(anyhow!(PackageUnableToLock(anyhow!(PackageNotInstalled( + pkg.clone() + ))))); + } + + info!("Locking package: {}@{}", pkg, version); + let versions = &mut config.lock_pkg_versions; + versions.insert(pkg.clone(), version.clone()); + } + + Ok(()) +} + +fn lock_installed_current_pkgs( + config: &mut Config, + release_service: &ReleaseService, +) -> anyhow::Result<()> { + for r in &release_service.list()? { + if !r.current { + continue; + } + + info!("Locking package: {}@{}", r.name, r.version); + let versions = &mut config.lock_pkg_versions; + versions.insert(r.name.clone(), r.version.clone()); + } + + Ok(()) +} + +fn display_locked_pkgs(config: &Config) -> anyhow::Result<()> { #[derive(Debug, Clone, Serialize, Deserialize)] struct PkgVersionInfo { name: String, - versions: Vec, + version: String, } let pkg_version_infos: Vec<_> = config .lock_pkg_versions .iter() - .map(|(name, versions)| PkgVersionInfo { + .map(|(name, version)| PkgVersionInfo { name: name.clone(), - versions: versions.clone(), + version: version.clone(), }) .collect(); + if pkg_version_infos.is_empty() { + return Err(anyhow!(NoPackagesLocked)); + } + output!( config.output_format, .display(stdout(), &pkg_version_infos, None, None) diff --git a/huber/src/cmd/repo.rs b/huber/src/cmd/repo.rs index 519eedbb..6a97463b 100644 --- a/huber/src/cmd/repo.rs +++ b/huber/src/cmd/repo.rs @@ -11,6 +11,7 @@ use log::info; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; +use crate::error::HuberError::{RepoAlreadyExist, RepoNotFound}; use crate::service::repo::RepoService; use crate::service::{ItemOperationAsyncTrait, ItemOperationTrait}; @@ -55,7 +56,7 @@ impl CommandTrait for RepoAddArgs { let repo_service = container.get::().unwrap(); if repo_service.has(&self.name)? { - return Err(anyhow!("{} repo already exists", self.name)); + return Err(anyhow!(RepoAlreadyExist(self.name.clone()))); } let repo = Repository { @@ -84,7 +85,7 @@ impl CommandTrait for RepoRemoveArgs { for repo in &self.name { if !repo_service.has(repo)? { - return Err(anyhow!("{} repo not found", repo)); + return Err(anyhow!(RepoNotFound(repo.clone()))); } info!("Removing repo {}", repo); diff --git a/huber/src/cmd/save.rs b/huber/src/cmd/save.rs index f8777279..5e2c7c74 100644 --- a/huber/src/cmd/save.rs +++ b/huber/src/cmd/save.rs @@ -1,13 +1,16 @@ use std::fs::File; use std::io::Write; +use anyhow::anyhow; use async_trait::async_trait; use clap::{Args, ValueHint}; +use filepath::FilePath; use huber_common::model::config::Config; use log::info; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; +use crate::error::HuberError::NoPackagesInstalled; use crate::service::release::ReleaseService; use crate::service::ItemOperationTrait; @@ -17,6 +20,7 @@ pub struct SaveArgs { help = "Save the list of installed 'current' packages to a file", long, num_args = 1, + default_value = "huber-packages.txt", value_hint = ValueHint::FilePath )] file: String, @@ -35,10 +39,17 @@ impl CommandTrait for SaveArgs { .map(|r| format!("{}@{}", r.package.name, r.version)) .collect(); + if versions.is_empty() { + return Err(anyhow!(NoPackagesInstalled)); + } + info!("Saving the package list to {}", self.file); let mut file = File::create(&self.file)?; file.write_all(versions.join("\n").as_bytes())?; - info!("Saved the package list to {}", self.file); + info!( + "Saved the package list to {}", + file.path()?.canonicalize()?.to_string_lossy() + ); Ok(()) } diff --git a/huber/src/cmd/search.rs b/huber/src/cmd/search.rs index 714a0398..f6fbbaf1 100644 --- a/huber/src/cmd/search.rs +++ b/huber/src/cmd/search.rs @@ -22,7 +22,7 @@ pub struct SearchArgs { )] name: Option, - #[arg(help = "Regex search", long, num_args = 1, value_hint = ValueHint::Unknown)] + #[arg(help = "Regex search", long, value_hint = ValueHint::Unknown)] pattern: bool, #[arg(help = "Package owner", long, num_args = 1, value_hint = ValueHint::Unknown)] @@ -32,7 +32,6 @@ pub struct SearchArgs { help = "Show all the released versions", long, requires = "name", - num_args = 1, value_hint = ValueHint::Unknown )] all: bool, diff --git a/huber/src/cmd/show.rs b/huber/src/cmd/show.rs index 0321567e..caab5345 100644 --- a/huber/src/cmd/show.rs +++ b/huber/src/cmd/show.rs @@ -10,6 +10,7 @@ use libcli_rs::output::{OutputFactory, OutputTrait}; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; +use crate::error::HuberError::{NoPackagesInstalled, PackageNotFound}; use crate::service::package::PackageService; use crate::service::release::{ReleaseService, ReleaseTrait}; use crate::service::{ItemOperationAsyncTrait, ItemOperationTrait}; @@ -70,7 +71,7 @@ impl CommandTrait for ShowArgs { }; if releases.is_empty() { - return Err(anyhow!("No package installed")); + return Err(anyhow!(NoPackagesInstalled)); } output!(config.output_format, .display( @@ -92,7 +93,7 @@ impl ShowArgs { release_service: &ReleaseService, ) -> anyhow::Result<()> { if !release_service.has(name)? { - return Err(anyhow!("Failed to find package {}", name)); + return Err(anyhow!(PackageNotFound(name.to_string()))); } let pkg = pkg_service.get(name)?; diff --git a/huber/src/cmd/uninstall.rs b/huber/src/cmd/uninstall.rs index 683bf3fc..e3aec77e 100644 --- a/huber/src/cmd/uninstall.rs +++ b/huber/src/cmd/uninstall.rs @@ -6,6 +6,7 @@ use log::info; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; +use crate::error::HuberError::PackageNotFound; use crate::service::package::PackageService; use crate::service::release::ReleaseService; use crate::service::ItemOperationTrait; @@ -24,7 +25,7 @@ impl CommandTrait for UninstallArgs { for name in self.name.iter() { if !pkg_service.has(name)? { - return Err(anyhow!("{} package not found", name)); + return Err(anyhow!(PackageNotFound(name.clone()))); } } diff --git a/huber/src/cmd/unlock.rs b/huber/src/cmd/unlock.rs index 6c7bbd7d..10502298 100644 --- a/huber/src/cmd/unlock.rs +++ b/huber/src/cmd/unlock.rs @@ -6,45 +6,75 @@ use log::info; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; +use crate::error::HuberError::{PackageNotFound, PackageNotInstalled}; use crate::service::config::{ConfigService, ConfigTrait}; use crate::service::package::PackageService; +use crate::service::release::ReleaseService; use crate::service::ItemOperationTrait; #[derive(Args)] pub struct UnlockArgs { - #[arg(help = "Package name", num_args = 1, value_hint = ValueHint::Unknown)] + #[arg( + help = "Package name", + num_args = 1, + group = "lock", + required = true, + value_hint = ValueHint::Unknown + )] name: Vec, + + #[arg( + help = "Unlock all the locked packages", + long, + group = "lock", + required = true, + value_hint = ValueHint::Unknown + )] + all: bool, } #[async_trait] impl CommandTrait for UnlockArgs { async fn run(&self, config: &Config, container: &DIContainer) -> anyhow::Result<()> { let pkg_service = container.get::().unwrap(); + let release_service = container.get::().unwrap(); + let config_service = container.get::().unwrap(); let mut config = config.clone(); - let mut require_update = false; - for pkg in &self.name { - if pkg_service.has(pkg)? { - return Err(anyhow!("Package {} not found", pkg)); - } + info!("Unlocking packages"); - if !require_update { - require_update = true; - } - - info!("Unlocking package: {}", pkg); - config.lock_pkg_versions.remove(pkg); + if self.all { + info!("Unlocking all packages"); + config.lock_pkg_versions.clear(); + } else { + unlock_pkgs(&mut config, pkg_service, release_service, &self.name)?; } - if !require_update { - info!("No packages to unlock"); - return Ok(()); - } - - let config_service = container.get::().unwrap(); config_service.update(&config)?; info!("Unlocked packages"); Ok(()) } } + +fn unlock_pkgs( + config: &mut Config, + pkg_service: &PackageService, + release_service: &ReleaseService, + pkgs: &Vec, +) -> anyhow::Result<()> { + for pkg in pkgs { + if !pkg_service.has(pkg)? { + return Err(anyhow!(PackageNotFound(pkg.clone()))); + } + + if !release_service.has(pkg)? { + return Err(anyhow!(PackageNotInstalled(pkg.clone()))); + } + + info!("Unlocking package: {}", pkg); + config.lock_pkg_versions.remove(pkg); + } + + Ok(()) +} diff --git a/huber/src/cmd/update.rs b/huber/src/cmd/update.rs index 40f3b469..d0495881 100644 --- a/huber/src/cmd/update.rs +++ b/huber/src/cmd/update.rs @@ -5,12 +5,15 @@ use anyhow::anyhow; use async_trait::async_trait; use clap::{Args, ValueHint}; use huber_common::model::config::Config; +use huber_common::model::package::Package; use huber_common::model::release::Release; -use log::info; +use log::{info, warn}; use maplit::hashmap; +use semver::Version; use simpledi_rs::di::{DIContainer, DIContainerTrait}; use crate::cmd::CommandTrait; +use crate::error::HuberError::{PackageNotInstalled, PackageUnableToUpdate}; use crate::service::package::PackageService; use crate::service::release::ReleaseService; use crate::service::{ItemOperationAsyncTrait, ItemOperationTrait}; @@ -23,7 +26,6 @@ pub struct UpdateArgs { #[arg( help = "Dry run to show available updates", long, - num_args = 1, value_hint = ValueHint::Unknown )] dryrun: bool, @@ -31,31 +33,20 @@ pub struct UpdateArgs { #[async_trait] impl CommandTrait for UpdateArgs { - async fn run(&self, _: &Config, container: &DIContainer) -> anyhow::Result<()> { + async fn run(&self, config: &Config, container: &DIContainer) -> anyhow::Result<()> { let release_service = container.get::().unwrap(); let pkg_service = container.get::().unwrap(); - let mut installed_latest_pkg_releases: HashMap = hashmap! {}; - - for ref name in self.name.iter() { - info!("Checking for updates for {}", name); + for name in self.name.iter() { if !release_service.has(name)? { - return Err(anyhow!( - "Unable to update {}, because it's not installed", - name - )); + return Err(anyhow!(PackageUnableToUpdate(anyhow!( + PackageNotInstalled(name.clone()) + )))); } } - for release in release_service.list()? { - if let Some(existing_release) = installed_latest_pkg_releases.get(&release.name) { - if release.compare(existing_release)? == Ordering::Greater { - installed_latest_pkg_releases.insert(release.name.clone(), release); - } - } else { - installed_latest_pkg_releases.insert(release.name.clone(), release); - } - } + let mut installed_latest_pkg_releases: HashMap = hashmap! {}; + get_installed_latest_pkg_releases(release_service, &mut installed_latest_pkg_releases)?; for (name, installed_release) in installed_latest_pkg_releases.iter() { info!( @@ -66,9 +57,17 @@ impl CommandTrait for UpdateArgs { let pkg = pkg_service.get(name)?; let new_release = release_service.get_latest(&pkg).await?; + info!( + "Found the latest version of {}: {}", + name, new_release.version + ); + if !is_pkg_updatable(config, &pkg, &new_release) { + continue; + } + if new_release.compare(installed_release)? == Ordering::Greater { info!( - "Found an update for {}. Installed version: {}, Latest version: {}", + "Updating package {} from {} to {}", name, installed_release.version, new_release.version ); update( @@ -79,8 +78,14 @@ impl CommandTrait for UpdateArgs { ) .await?; info!( - "Successfully updated {}. Installed version: {}, Latest version: {}", - name, installed_release.version, new_release.version + "Package {} updated to {} successfully", + name, new_release.version + ); + } else { + info!( + "Nothing to update, as the currently installed version ({}) is equal to or \ + higher than the found version ({})", + installed_release.version, new_release.version ); } } @@ -89,6 +94,40 @@ impl CommandTrait for UpdateArgs { } } +fn get_installed_latest_pkg_releases( + release_service: &ReleaseService, + installed_latest_pkg_releases: &mut HashMap, +) -> anyhow::Result<()> { + for release in release_service.list()? { + if let Some(existing_release) = installed_latest_pkg_releases.get(&release.name) { + if release.compare(existing_release)? == Ordering::Greater { + installed_latest_pkg_releases.insert(release.name.clone(), release); + } + } else { + installed_latest_pkg_releases.insert(release.name.clone(), release); + } + } + Ok(()) +} + +fn is_pkg_updatable(config: &Config, pkg: &Package, latest_release: &Release) -> bool { + if let Some(lock_version) = config.lock_pkg_versions.get(&pkg.name) { + let lock_version = Version::parse(lock_version.trim_start_matches("v")).unwrap(); + let latest_version = + Version::parse(latest_release.version.trim_start_matches("v")).unwrap(); + + if latest_version.cmp(&lock_version) == Ordering::Greater { + warn!( + "Package {} is locked to version {}. Skipping update", + pkg.name, lock_version + ); + return false; + } + } + + true +} + async fn update( release_service: &ReleaseService, dryrun: bool, diff --git a/huber/src/error.rs b/huber/src/error.rs index 59cef4c1..5ed883be 100644 --- a/huber/src/error.rs +++ b/huber/src/error.rs @@ -2,6 +2,30 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum HuberError { - #[error("config not found: {0:?}")] + #[error("Config not found: {0:?}")] ConfigNotFound(String), + + #[error("Package not found: {0:?}")] + PackageNotFound(String), + + #[error("Package not installed: {0:?}")] + PackageNotInstalled(String), + + #[error("Repository already exists: {0:?}")] + RepoAlreadyExist(String), + + #[error("Repository not found: {0:?}")] + RepoNotFound(String), + + #[error("No packages installed")] + NoPackagesInstalled, + + #[error("Package unable to update")] + PackageUnableToUpdate(#[source] anyhow::Error), + + #[error("Package unable to lock")] + PackageUnableToLock(#[source] anyhow::Error), + + #[error("No packages locked")] + NoPackagesLocked, } diff --git a/huber/src/opt.rs b/huber/src/opt.rs index 574e24c7..243d51cd 100644 --- a/huber/src/opt.rs +++ b/huber/src/opt.rs @@ -1,16 +1,17 @@ use anyhow::anyhow; +use semver::Version; /// Parse package name and version /// /// # Examples /// /// ``` -/// use huber::opt::parse_pkg_name_version; -/// let (name, version) = parse_pkg_name_version("package-name@version").unwrap(); +/// use huber::opt::parse_pkg_name_semver; +/// let (name, version) = parse_pkg_name_semver("package-name@1.2.3").unwrap(); /// assert_eq!(name, "package-name"); -/// assert_eq!(version, "version"); +/// assert_eq!(version, "v1.2.3"); /// ``` -pub fn parse_pkg_name_version(name_version: &str) -> anyhow::Result<(String, String)> { +pub fn parse_pkg_name_semver(name_version: &str) -> anyhow::Result<(String, String)> { let result: Vec<_> = name_version.splitn(2, '@').collect(); if result.len() != 2 { @@ -19,5 +20,8 @@ pub fn parse_pkg_name_version(name_version: &str) -> anyhow::Result<(String, Str )); } - Ok((result[0].to_string(), result[1].to_string())) + let (name, version) = (result[0].to_string(), result[1].to_string()); + Version::parse(&version)?; + + Ok((name, format!("v{}", version))) } diff --git a/huber/src/service/cache.rs b/huber/src/service/cache.rs index 18db8940..a5f73852 100644 --- a/huber/src/service/cache.rs +++ b/huber/src/service/cache.rs @@ -14,6 +14,7 @@ use rayon::prelude::*; use regex::Regex; use simpledi_rs::di::{DIContainer, DIContainerExtTrait, DependencyInjectTrait}; +use crate::error::HuberError::PackageNotFound; use crate::github::{GithubClient, GithubClientTrait}; use crate::service::repo::{RepoAsyncTrait, RepoService, RepoTrait}; use crate::service::{ItemOperationTrait, ServiceTrait}; @@ -65,7 +66,7 @@ impl CacheService { impl CacheTrait for CacheService { fn get_package(&self, name: &str) -> anyhow::Result { if !self.has_package(name)? { - return Err(anyhow!("{} not found", name)); + return Err(anyhow!(PackageNotFound(name.into()))); } let config = self.container.get::().unwrap(); @@ -86,7 +87,7 @@ impl CacheTrait for CacheService { .into_iter() .find(|it| it.name == name) { - None => Err(anyhow!("{} not found", name)), + None => Err(anyhow!(PackageNotFound(name.into()))), Some(pkg) => Ok(pkg), } } diff --git a/huber/src/service/package.rs b/huber/src/service/package.rs index a263f95e..341a824a 100644 --- a/huber/src/service/package.rs +++ b/huber/src/service/package.rs @@ -5,9 +5,10 @@ use async_trait::async_trait; use huber_common::model::config::{Config, ConfigFieldConvertTrait}; use huber_common::model::package::{Package, PackageSource, PackageSummary}; use huber_common::model::release::{ReleaseKind, SortModelTrait}; -use log::{debug, error}; +use log::debug; use simpledi_rs::di::{DIContainer, DIContainerExtTrait, DependencyInjectTrait}; +use crate::error::HuberError::PackageNotFound; use crate::github::{GithubClient, GithubClientTrait}; use crate::service::cache::{CacheService, CacheTrait}; use crate::service::{ItemOperationAsyncTrait, ItemOperationTrait, ItemSearchTrait, ServiceTrait}; @@ -88,7 +89,7 @@ impl ItemOperationTrait for PackageService { if !results.is_empty() { Ok(results.first().unwrap().to_owned()) } else { - Err(anyhow!("{} not found", name)) + Err(anyhow!(PackageNotFound(name.into()))) } } } @@ -160,7 +161,7 @@ impl ItemSearchTrait for PackageService { match cache_service.get_package(name) { Ok(pkg) => found_items.push(pkg), - Err(err) => error!("{}", err), + Err(err) => debug!("Package not found: {}", err), } return Ok(found_items); diff --git a/huber/src/service/release.rs b/huber/src/service/release.rs index b1f7a9b7..2aa7ea2b 100644 --- a/huber/src/service/release.rs +++ b/huber/src/service/release.rs @@ -14,7 +14,7 @@ use huber_common::model::package::{GithubPackage, Package, PackageDetailType, Pa use huber_common::model::release::{Release, ReleaseIndex}; use huber_common::str::OsStrExt; use is_executable::IsExecutable; -use log::{debug, error, info}; +use log::{debug, info}; use maplit::hashmap; use regex::{Captures, Regex}; use simpledi_rs::di::{DIContainer, DIContainerExtTrait, DependencyInjectTrait}; @@ -657,6 +657,9 @@ impl ReleaseAsyncTrait for ReleaseService { debug!("Setting the current release: {}", &release); let config = self.container.get::().unwrap(); + + //TODO check locked version + release.current = true; release.name = release.package.name.clone(); @@ -808,14 +811,14 @@ impl ItemOperationTrait for ReleaseService { Ok(f) => { releases.push(serde_yaml::from_reader(f)?); } - Err(e) => error!( + Err(e) => debug!( "Failed to read {:?} and ignored from the installed release list: {}", p, e ), } } Err(e) => { - error!("Failed to get {} package because the package probably removed from the repositories: {}", &ri.name, e); + debug!("Failed to get {} package because the package probably removed from the repositories: {}", &ri.name, e); continue; } } diff --git a/huber/tests/common/mod.rs b/huber/tests/common/mod.rs index 7d5108d2..749626d8 100644 --- a/huber/tests/common/mod.rs +++ b/huber/tests/common/mod.rs @@ -1 +1,78 @@ +use std::path::Path; + +use assert_cmd::assert::Assert; +use assert_cmd::Command; + pub const HUBER_EXEC: &str = env!("CARGO_BIN_EXE_huber"); +pub fn install_pkg(name: &str) -> Assert { + Command::new(HUBER_EXEC) + .arg("install") + .arg(name) + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success() +} + +pub fn uninstall_pkg(name: &str) -> Assert { + Command::new(HUBER_EXEC) + .arg("uninstall") + .arg(name) + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success() +} + +pub fn save_pkg_list(file: &str) -> Assert { + Command::new(HUBER_EXEC) + .arg("save") + .arg("--file") + .arg(file) + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success() +} + +pub fn reset_huber() -> Assert { + Command::new(HUBER_EXEC) + .arg("reset") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success() +} + +macro_rules! assert_eq_last_line { + ($arr:expr, $str:expr) => { + let line = String::from_utf8($arr.clone()) + .unwrap() + .lines() + .last() + .unwrap() + .to_string(); + + assert_eq!(line, $str) + }; +} diff --git a/huber/tests/config.rs b/huber/tests/config.rs index 7ac94f89..04500f19 100644 --- a/huber/tests/config.rs +++ b/huber/tests/config.rs @@ -1,12 +1,19 @@ use assert_cmd::Command; +use scopeguard::defer; use tempfile::tempdir; mod common; use common::HUBER_EXEC; +use crate::common::reset_huber; + #[test] fn test_config_not_found() { + defer! { + reset_huber(); + } + Command::new(HUBER_EXEC) .arg("config") .arg("show") @@ -20,6 +27,10 @@ fn test_config_not_found() { #[test] fn test_config_save_and_found() { + defer! { + reset_huber(); + } + let github_token = "token"; let github_base_uri = "uri"; let github_key = "key"; diff --git a/huber/tests/current.rs b/huber/tests/current.rs new file mode 100644 index 00000000..74af7d03 --- /dev/null +++ b/huber/tests/current.rs @@ -0,0 +1,57 @@ +use std::path::Path; + +use assert_cmd::Command; +use scopeguard::defer; + +use crate::common::{install_pkg, reset_huber, HUBER_EXEC}; + +#[macro_use] +mod common; + +#[test] +fn test_current() { + defer! { + reset_huber(); + } + + install_pkg("k9s@v0.32.7"); + + let assert = Command::new(HUBER_EXEC) + .arg("current") + .arg("k9s@v0.32.7") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success(); + + assert_eq_last_line!( + assert.get_output().stderr, + "[INFO ] k9s@v0.32.7 is now the current version" + ); +} + +#[test] +fn test_current_fail() { + defer! { + reset_huber(); + } + + Command::new(HUBER_EXEC) + .arg("current") + .arg("pkg_notfound@1.1.1") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .failure() + .stderr("[WARN ] package not installed: \"pkg_notfound\"\n"); +} diff --git a/huber/tests/flush.rs b/huber/tests/flush.rs new file mode 100644 index 00000000..69782f49 --- /dev/null +++ b/huber/tests/flush.rs @@ -0,0 +1,56 @@ +use std::path::Path; + +use assert_cmd::Command; +use scopeguard::defer; + +use crate::common::{install_pkg, reset_huber, HUBER_EXEC}; + +#[macro_use] +mod common; + +#[test] +fn test_flush_nothing() { + defer! { + reset_huber(); + } + + Command::new(HUBER_EXEC) + .arg("flush") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success() + .stderr("[INFO ] Nothing to flush\n"); +} + +#[test] +fn test_flush() { + defer! { + reset_huber(); + } + + install_pkg("k9s@v0.32.6"); + install_pkg("k9s@v0.32.7"); + + let assert = Command::new(HUBER_EXEC) + .arg("flush") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success(); + + assert_eq_last_line!( + assert.get_output().stderr, + "[INFO ] k9s (version: v0.32.6, source: github) removed" + ); +} diff --git a/huber/tests/info.rs b/huber/tests/info.rs new file mode 100644 index 00000000..3530a326 --- /dev/null +++ b/huber/tests/info.rs @@ -0,0 +1,53 @@ +#![cfg(test)] + +use std::path::Path; + +use assert_cmd::Command; +use scopeguard::defer; + +use crate::common::{install_pkg, reset_huber, HUBER_EXEC}; + +mod common; + +#[test] +fn test_info() { + defer! { + reset_huber(); + } + + install_pkg("k9s@v0.32.7"); + + Command::new(HUBER_EXEC) + .arg("info") + .arg("k9s") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success(); +} + +#[test] +fn test_info_fail() { + defer! { + reset_huber(); + } + + Command::new(HUBER_EXEC) + .arg("info") + .arg("pkg_notfound") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .failure() + .stderr("[WARN ] package not found: \"pkg_notfound\"\n"); +} diff --git a/huber/tests/install.rs b/huber/tests/install.rs new file mode 100644 index 00000000..b02a556e --- /dev/null +++ b/huber/tests/install.rs @@ -0,0 +1,40 @@ +use std::path::Path; + +use assert_cmd::Command; +use scopeguard::defer; + +use crate::common::{install_pkg, reset_huber, HUBER_EXEC}; + +#[macro_use] +mod common; + +#[test] +fn test_install() { + defer! { + reset_huber(); + } + + let assert = install_pkg("k9s"); + assert_eq_last_line!(assert.get_output().stderr, "[INFO ] k3s@latest installed"); +} + +#[test] +fn test_install_fail() { + defer! { + reset_huber(); + } + + Command::new(HUBER_EXEC) + .arg("install") + .arg("pkg_notfound@0.1.0") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .failure() + .stderr("[WARN ] package not found: \"pkg_notfound\"\n"); +} diff --git a/huber/tests/load.rs b/huber/tests/load.rs new file mode 100644 index 00000000..15020458 --- /dev/null +++ b/huber/tests/load.rs @@ -0,0 +1,41 @@ +use std::fs; +use std::path::Path; + +use assert_cmd::Command; +use scopeguard::defer; + +use crate::common::{install_pkg, reset_huber, save_pkg_list, HUBER_EXEC}; + +#[macro_use] +mod common; + +#[test] +fn test_load() { + defer! { + reset_huber(); + } + + let file_name = "huber-packages.txt"; + defer!(fs::remove_file(file_name).unwrap();); + + install_pkg("k9s"); + save_pkg_list(file_name); + + let assert = Command::new(HUBER_EXEC) + .arg("load") + .arg("--file") + .arg(file_name) + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .success(); + assert_eq_last_line!( + assert.get_output().stderr, + "[INFO ] Installed packages: total 1" + ); +} diff --git a/huber/tests/lock.rs b/huber/tests/lock.rs new file mode 100644 index 00000000..23c39dcf --- /dev/null +++ b/huber/tests/lock.rs @@ -0,0 +1,51 @@ +use std::path::Path; + +use assert_cmd::Command; +use scopeguard::defer; + +use crate::common::{install_pkg, reset_huber, HUBER_EXEC}; + +#[macro_use] +mod common; + +#[test] +fn test_lock_fail() { + defer! { + reset_huber(); + } + + Command::new(HUBER_EXEC) + .arg("lock") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .failure() + .stderr("[WARN ] no packages locked\n"); +} + +#[test] +fn test_lock() { + defer! { + reset_huber(); + } + + install_pkg("k9s@v0.32.7"); + + Command::new(HUBER_EXEC) + .arg("lock") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .failure() + .stderr("[INFO ] Packages locked successfully\n"); +} diff --git a/huber/tests/reset.rs b/huber/tests/reset.rs new file mode 100644 index 00000000..33c05475 --- /dev/null +++ b/huber/tests/reset.rs @@ -0,0 +1,10 @@ +use crate::common::reset_huber; + +#[macro_use] +mod common; + +#[test] +fn test_reset() { + let assert = reset_huber(); + assert_eq_last_line!(assert.get_output().stderr, "[INFO ] Huber reset"); +} diff --git a/huber/tests/save.rs b/huber/tests/save.rs new file mode 100644 index 00000000..064fe2a6 --- /dev/null +++ b/huber/tests/save.rs @@ -0,0 +1,34 @@ +use std::fs; +use std::path::Path; + +use scopeguard::defer; + +use crate::common::{install_pkg, reset_huber, save_pkg_list}; + +#[macro_use] +mod common; + +#[test] +fn test_save() { + defer! { + reset_huber(); + } + + install_pkg("k9s"); + + let file_name = "huber-packages.txt"; + let file_path = Path::new(file_name); + defer! { + let _ = fs::remove_file(file_path); + }; + + let result = save_pkg_list(file_name); + assert_eq_last_line!( + result.get_output().stderr, + format!( + "[INFO ] Saved the package list to {}", + file_path.canonicalize().unwrap().to_string_lossy() + ) + ); + assert!(fs::exists(file_path).unwrap()); +} diff --git a/huber/tests/uninstall.rs b/huber/tests/uninstall.rs new file mode 100644 index 00000000..90ac2c09 --- /dev/null +++ b/huber/tests/uninstall.rs @@ -0,0 +1,43 @@ +use std::path::Path; + +use assert_cmd::Command; +use common::{install_pkg, uninstall_pkg, HUBER_EXEC}; +use scopeguard::defer; + +use crate::common::reset_huber; + +#[macro_use] +mod common; + +#[test] +fn test_uninstall() { + defer! { + reset_huber(); + } + + install_pkg("k3s"); + + let assert = uninstall_pkg("k3s"); + assert_eq_last_line!(assert.get_output().stderr, "[INFO ] Uninstalled k3s"); +} + +#[test] +fn test_uninstall_fail() { + defer! { + reset_huber(); + } + + Command::new(HUBER_EXEC) + .arg("uninstall") + .arg("pkg_notfound") + .env( + "MANAGED_PKG_ROOT_DIR", + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("generated"), + ) + .assert() + .failure() + .stderr("[WARN ] package not found: \"pkg_notfound\"\n"); +} diff --git a/justfile b/justfile index 14c8e08b..ee3316e8 100644 --- a/justfile +++ b/justfile @@ -10,7 +10,7 @@ github_key := env('GITHUB_KEY', '') # Build binaries build cmd_opts='': fix fmt - cargo {{ cargo_opts }} build {{ cmd_opts }} + @cargo {{ cargo_opts }} build {{ cmd_opts }} # Run tests test: @@ -74,8 +74,8 @@ install: @mkdir -p ~/.huber/bin && cp ~/.cargo/bin/huber ~/.huber/bin && {{ prj_dir }}/hack/add-huber-bin-to-env.sh # (local dev) Run commands using the built Huber with the local package generated folder -run huber_cmd pkg_dir=managed_pkg_root_dir: - @MANAGED_PKG_ROOT_DIR={{ pkg_dir }} {{ huber_exec }} {{ huber_cmd }} +@run huber_cmd pkg_dir=managed_pkg_root_dir: + MANAGED_PKG_ROOT_DIR={{ pkg_dir }} {{ huber_exec }} {{ huber_cmd }} # (local dev) Run commands using the installed Huber with the local package generated folder run-installed huber_cmd pkg_dir=managed_pkg_root_dir: