diff --git a/src/currentprocess.rs b/src/currentprocess.rs index 115c678871..e9cfafa7d2 100644 --- a/src/currentprocess.rs +++ b/src/currentprocess.rs @@ -208,6 +208,16 @@ impl TestProcess { }) } + /// Extracts the stdout from the process + pub fn stdout(&self) -> Vec { + let tp = match &self.process { + Process::TestProcess(tp) => tp, + _ => unreachable!(), + }; + + tp.stdout.lock().unwrap_or_else(|e| e.into_inner()).clone() + } + /// Extracts the stderr from the process pub fn stderr(&self) -> Vec { let tp = match &self.process { diff --git a/src/test/mock/clitools.rs b/src/test/mock/clitools.rs index 9c3236fcb6..15a3986de5 100644 --- a/src/test/mock/clitools.rs +++ b/src/test/mock/clitools.rs @@ -16,8 +16,11 @@ use std::{ use enum_map::{enum_map, Enum, EnumMap}; use once_cell::sync::Lazy; +use tokio::runtime::Builder; use url::Url; +use crate::cli::rustup_mode; +use crate::currentprocess; use crate::test as rustup_test; use crate::test::const_dist_dir; use crate::test::this_host_triple; @@ -676,8 +679,13 @@ impl Config { I: IntoIterator + Clone + Debug, A: AsRef, { + let inprocess = allow_inprocess(name, args.clone()); let start = Instant::now(); - let out = self.run_subprocess(name, args.clone(), env); + let out = if inprocess { + self.run_inprocess(name, args.clone(), env) + } else { + self.run_subprocess(name, args.clone(), env) + }; let duration = Instant::now() - start; let output = SanitizedOutput { ok: matches!(out.status, Some(0)), @@ -686,6 +694,7 @@ impl Config { }; println!("ran: {} {:?}", name, args); + println!("inprocess: {inprocess}"); println!("status: {:?}", out.status); println!("duration: {:.3}s", duration.as_secs_f32()); println!("stdout:\n====\n{}\n====\n", output.stdout); @@ -694,6 +703,55 @@ impl Config { output } + #[cfg_attr(feature = "otel", tracing::instrument(skip_all))] + pub(crate) fn run_inprocess(&self, name: &str, args: I, env: &[(&str, &str)]) -> Output + where + I: IntoIterator, + A: AsRef, + { + // should we use vars_os, or skip over non-stringable vars? This is test + // code after all... + let mut vars: HashMap = HashMap::default(); + self::env(self, &mut vars); + vars.extend(env.iter().map(|(k, v)| (k.to_string(), v.to_string()))); + let mut arg_strings: Vec> = Vec::new(); + arg_strings.push(name.to_owned().into_boxed_str()); + for arg in args { + arg_strings.push( + arg.as_ref() + .to_os_string() + .into_string() + .unwrap() + .into_boxed_str(), + ); + } + let mut builder = Builder::new_multi_thread(); + builder + .enable_all() + .worker_threads(2) + .max_blocking_threads(2); + let rt = builder.build().unwrap(); + rt.block_on(async { + let tp = + currentprocess::TestProcess::new(&*self.workdir.borrow(), &arg_strings, vars, ""); + let process_res = + rustup_mode::main(tp.process.current_dir().unwrap(), &tp.process).await; + // convert Err's into an ec + let ec = match process_res { + Ok(process_res) => process_res, + Err(e) => { + crate::cli::common::report_error(&e, &tp.process); + utils::ExitCode(1) + } + }; + Output { + status: Some(ec.0), + stderr: tp.stderr(), + stdout: tp.stdout(), + } + }) + } + #[track_caller] pub fn run_subprocess(&self, name: &str, args: I, env: &[(&str, &str)]) -> Output where @@ -797,6 +855,43 @@ pub fn env(config: &Config, cmd: &mut E) { config.env(cmd) } +fn allow_inprocess(name: &str, args: I) -> bool +where + I: IntoIterator, + A: AsRef, +{ + // Only the rustup alias is currently ready for in-process testing: + // - -init performs self-update which monkey with global external state. + // - proxies themselves behave appropriately the proxied output needs to be + // collected for assertions to be made on it as our tests traverse layers. + // - self update executions cannot run in-process because on windows the + // process replacement dance would replace the test process. + // - any command with --version in it is testing to see something was + // installed properly, so we have to shell out to it to be sure + if name != "rustup" { + return false; + } + let mut is_update = false; + let mut no_self_update = false; + let mut self_cmd = false; + let mut run = false; + let mut version = false; + for arg in args { + if arg.as_ref() == "update" { + is_update = true; + } else if arg.as_ref() == "--no-self-update" { + no_self_update = true; + } else if arg.as_ref() == "self" { + self_cmd = true; + } else if arg.as_ref() == "run" { + run = true; + } else if arg.as_ref() == "--version" { + version = true; + } + } + !(run || self_cmd || version || (is_update && !no_self_update)) +} + #[derive(Copy, Clone, Eq, PartialEq)] enum RlsStatus { Available,