From 69176e855cdab751684c015c82f7f1e12da3a4e7 Mon Sep 17 00:00:00 2001 From: Alfie John Date: Tue, 3 Sep 2024 00:40:31 +1000 Subject: [PATCH] Prompt user to install toolchain if missing (#652) --- src/fmt.rs | 10 +++++++++ src/fuelup_cli.rs | 44 ++++++++++++++++++++++++++++++++++++-- tests/default.rs | 43 ++++++++++++++++++++++++++++++++++--- tests/show.rs | 50 ++++++++++++++++++-------------------------- tests/testcfg/mod.rs | 49 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 157 insertions(+), 39 deletions(-) diff --git a/src/fmt.rs b/src/fmt.rs index a17d00a3..96ee0f3b 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -57,4 +57,14 @@ pub fn ask_user_yes_no_question(question: &str) -> io::Result { return Ok(result); } } + + // This is the dialoguer version of the prompter, but it's not testable + // as dialoguer requires an interactive terminal to work without failing. + // + // use dialoguer::{theme::ColorfulTheme, Confirm}; + // + // Confirm::with_theme(&ColorfulTheme::default()) + // .with_prompt(question) + // .interact() + // .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string())) } diff --git a/src/fuelup_cli.rs b/src/fuelup_cli.rs index 4c97fc7f..a8e12a6a 100644 --- a/src/fuelup_cli.rs +++ b/src/fuelup_cli.rs @@ -7,9 +7,14 @@ use crate::commands::{ toolchain::{self, ToolchainCommand}, upgrade::{self, UpgradeCommand}, }; -use crate::ops::{fuelup_show, fuelup_update}; -use anyhow::Result; +use anyhow::{bail, Context, Result}; use clap::Parser; +use crate::fmt::ask_user_yes_no_question; +use crate::ops::{fuelup_show, fuelup_toolchain, fuelup_update}; +use crate::toolchain::{DistToolchainDescription, Toolchain}; +use crate::toolchain_override::ToolchainOverride; +use std::str::FromStr; +use tracing::info; #[derive(Debug, Parser)] #[clap(name = "fuelup", about = "Fuel Toolchain Manager", version)] @@ -46,6 +51,41 @@ enum Commands { pub fn fuelup_cli() -> Result<()> { let cli = Cli::parse(); + if let Some(toolchain_override) = ToolchainOverride::from_project_root() { + let override_path = toolchain_override.cfg.toolchain.channel.to_string(); + let toolchain = match DistToolchainDescription::from_str(&override_path) { + Ok(desc) => Toolchain::from_path(&desc.to_string()), + Err(_) => Toolchain::from_path(&override_path), + }; + + info!("Using override toolchain '{}'", &toolchain.name); + + if !toolchain.exists() { + match cli.command { + Commands::Toolchain(_) => { + // User is managing their toolchains, so we fall through + } + _ => { + let should_install = ask_user_yes_no_question( + "Override toolchain is not installed. Do you want to install it now?", + ) + .context("Console I/O")?; + + if should_install { + fuelup_toolchain::install::install(toolchain::InstallCommand { + name: toolchain.name, + })?; + } else { + bail!( + "Override toolchain is not installed. Please run: 'fuelup toolchain install {}'", + &toolchain.name, + ) + } + } + } + } + } + match cli.command { Commands::Check(command) => check::exec(command), Commands::Completions(command) => completions::exec(command), diff --git a/tests/default.rs b/tests/default.rs index 88930954..cd97aa87 100644 --- a/tests/default.rs +++ b/tests/default.rs @@ -99,13 +99,50 @@ fn fuelup_default_nightly_and_nightly_date() -> Result<()> { Ok(()) } +#[test] +fn fuelup_default_override_skip_install() -> Result<()> { + testcfg::setup(FuelupState::LatestWithBetaOverride, &|cfg| { + let output = cfg.fuelup_with_input(&["default"], b"N\n"); + let triple = TargetTriple::from_host().unwrap(); + + assert!(output + .stdout + .starts_with(&format!("Using override toolchain 'beta-1-{triple}'"))); + + assert!(output + .stdout + .contains("Override toolchain is not installed. Do you want to install it now?")); + + assert!(output + .stdout + .contains(&format!("Override toolchain is not installed. Please run: 'fuelup toolchain install beta-1-{triple}'"))); + })?; + Ok(()) +} + #[test] fn fuelup_default_override() -> Result<()> { testcfg::setup(FuelupState::LatestWithBetaOverride, &|cfg| { - let output = cfg.fuelup(&["default"]); + let output = cfg.fuelup_with_input(&["default"], b"Y\n"); let triple = TargetTriple::from_host().unwrap(); - let expected_stdout = format!("beta-1-{triple} (override), latest-{triple} (default)\n"); - assert_eq!(output.stdout, expected_stdout); + + assert!(output + .stdout + .starts_with(&format!("Using override toolchain 'beta-1-{triple}'"))); + + assert!(output + .stdout + .contains("Override toolchain is not installed. Do you want to install it now?")); + + assert!(output.stdout.contains("Downloading:")); + + assert!(output + .stdout + .contains("The Fuel toolchain is installed and up to date")); + + assert!(output + .stdout + .contains(&format!("beta-1-{triple} (override)"))); })?; Ok(()) } diff --git a/tests/show.rs b/tests/show.rs index 6a557f7f..d8e027e0 100644 --- a/tests/show.rs +++ b/tests/show.rs @@ -173,39 +173,28 @@ fn fuelup_show_custom() -> Result<()> { #[test] fn fuelup_show_override() -> Result<()> { testcfg::setup(FuelupState::LatestWithBetaOverride, &|cfg| { - let stripped = strip_ansi_escapes::strip(cfg.fuelup(&["show"]).stdout); + let stripped = strip_ansi_escapes::strip(cfg.fuelup_with_input(&["show"], b"y\n").stdout); let stdout = String::from_utf8_lossy(&stripped); let target = TargetTriple::from_host().unwrap(); - let fuelup_home = cfg.fuelup_dir(); - let fuelup_home_str = fuelup_home.to_string_lossy(); - let expected_stdout = formatdoc! { - r#"Default host: {target} - fuelup home: {fuelup_home_str} - Installed toolchains - -------------------- - latest-{target} (default) + assert!(stdout + .starts_with(&format!("Using override toolchain 'beta-1-{target}'"))); - active toolchain - ---------------- - beta-1-{target} (override), path: {} - forc : not found - - forc-client - - forc-deploy : not found - - forc-run : not found - - forc-crypto : not found - - forc-debug : not found - - forc-doc : not found - - forc-fmt : not found - - forc-lsp : not found - - forc-tx : not found - - forc-wallet : not found - fuel-core : not found - fuel-core-keygen : not found - "#, cfg.home.join(FUEL_TOOLCHAIN_TOML_FILE).display() - }; - assert_eq!(stdout, expected_stdout); - })?; + assert!(stdout + .contains("Override toolchain is not installed. Do you want to install it now?")); + + assert!(stdout.contains("Downloading:")); + + assert!(stdout.contains(&formatdoc! {" + Installed: + - forc 0.26.0 + - forc-wallet 0.1.2 + - fuel-core 0.10.1 + "})); + + assert!(stdout + .contains(&format!("beta-1-{target} (override)"))); + })?; Ok(()) } @@ -267,7 +256,8 @@ fn fuelup_show_latest_then_override() -> Result<()> { stdout = String::from_utf8_lossy(&stripped); let expected_stdout = formatdoc! { - r#"Default host: {target} + r#"Using override toolchain 'nightly-2022-08-30-{target}' + Default host: {target} fuelup home: {fuelup_home_str} Installed toolchains diff --git a/tests/testcfg/mod.rs b/tests/testcfg/mod.rs index 43bd7047..cb4721a3 100644 --- a/tests/testcfg/mod.rs +++ b/tests/testcfg/mod.rs @@ -1,17 +1,17 @@ use anyhow::Result; -use fuelup::channel::{BETA_1, LATEST, NIGHTLY}; -use fuelup::constants::FUEL_TOOLCHAIN_TOML_FILE; +use fuelup::constants::{BETA_1, FUEL_TOOLCHAIN_TOML_FILE, LATEST, NIGHTLY}; use fuelup::file::hard_or_symlink_file; use fuelup::settings::SettingsFile; use fuelup::target_triple::TargetTriple; use fuelup::toolchain_override::{self, OverrideCfg, ToolchainCfg, ToolchainOverride}; use semver::Version; +use std::io::Write; use std::os::unix::fs::OpenOptionsExt; use std::str::FromStr; use std::{ env, fs, path::{Path, PathBuf}, - process::{Command, ExitStatus}, + process::{Command, ExitStatus, Stdio}, }; use tempfile::tempdir; @@ -130,7 +130,7 @@ impl TestCfg { .args(args) .current_dir(&self.home) .env("HOME", &self.home) - .env("CARGO_HOME", &self.home.join(".cargo")) + .env("CARGO_HOME", self.home.join(".cargo")) .env( "PATH", format!( @@ -163,6 +163,47 @@ impl TestCfg { pub fn fuelup(&mut self, args: &[&str]) -> TestOutput { self.exec("fuelup", args) } + + /// A convenience wrapper for executing 'fuelup' within the fuelup test configuration. + /// It takes an array ref of bytes to write into stdin. + pub fn fuelup_with_input(&mut self, args: &[&str], input: &[u8]) -> TestOutput { + let path = self.fuelup_bin_dirpath.join("fuelup"); + let mut child = Command::new(path) + .args(args) + .current_dir(&self.home) + .env("HOME", &self.home) + .env("CARGO_HOME", self.home.join(".cargo")) + .env( + "PATH", + format!( + "{}:{}:{}", + &self.home.join(".local/bin").display(), + &self.home.join(".cargo/bin").display(), + &self.home.join(".fuelup/bin").display(), + ), + ) + .env("TERM", "dumb") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) // Inherit so we can see it install + .spawn() + .expect("Failed to execute command"); + + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + + for byte in input { + stdin.write_all(&[*byte]).expect("Failed to write to stdin"); + } + + let output = child.wait_with_output().expect("Failed to read output"); + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + + TestOutput { + stdout, + stderr, + status: output.status, + } + } } #[cfg(unix)]