Skip to content

Commit 332eb45

Browse files
committed
run rustup tests in-process
There's a certain amount of hackiness in making this work, because a number of our tests are intrinsically hostile to in-process testing, but generally speaking it works well.
1 parent 0997247 commit 332eb45

File tree

3 files changed

+176
-23
lines changed

3 files changed

+176
-23
lines changed

src/cli/topical_doc.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use crate::errors::*;
21
use std::ffi::OsString;
32
use std::fs;
43
use std::path::{Path, PathBuf};
54

5+
use super::errors::*;
6+
67
struct DocData<'a> {
78
topic: &'a str,
89
subtopic: &'a str,

src/currentprocess.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,22 @@ impl TestProcess {
194194
let high_bits = rng.gen_range(0, u32::MAX) as u64;
195195
high_bits << 32 | low_bits
196196
}
197+
198+
/// Extracts the stdout from the process
199+
pub fn get_stdout(&self) -> Vec<u8> {
200+
self.stdout
201+
.lock()
202+
.unwrap_or_else(|e| e.into_inner())
203+
.clone()
204+
}
205+
206+
/// Extracts the stderr from the process
207+
pub fn get_stderr(&self) -> Vec<u8> {
208+
self.stderr
209+
.lock()
210+
.unwrap_or_else(|e| e.into_inner())
211+
.clone()
212+
}
197213
}
198214

199215
impl ProcessSource for TestProcess {

tests/mock/clitools.rs

Lines changed: 158 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
//! A mock distribution server used by tests/cli-v1.rs and
22
//! tests/cli-v2.rs
3-
4-
use crate::mock::dist::{
5-
change_channel_date, ManifestVersion, MockChannel, MockComponent, MockDistServer, MockPackage,
6-
MockTargetedPackage,
7-
};
8-
use crate::mock::topical_doc_data;
9-
use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder};
10-
use lazy_static::lazy_static;
113
use std::cell::RefCell;
124
use std::collections::HashMap;
135
use std::env;
@@ -18,8 +10,21 @@ use std::io;
1810
use std::path::{Path, PathBuf};
1911
use std::process::Command;
2012
use std::sync::Arc;
13+
14+
use lazy_static::lazy_static;
2115
use url::Url;
2216

17+
use rustup::cli::rustup_mode;
18+
use rustup::currentprocess;
19+
use rustup::utils::utils;
20+
21+
use crate::mock::dist::{
22+
change_channel_date, ManifestVersion, MockChannel, MockComponent, MockDistServer, MockPackage,
23+
MockTargetedPackage,
24+
};
25+
use crate::mock::topical_doc_data;
26+
use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder};
27+
2328
/// The configuration used by the tests in this module
2429
pub struct Config {
2530
/// Where we put the rustup / rustc / cargo bins
@@ -363,6 +368,12 @@ fn print_indented(heading: &str, text: &str) {
363368
);
364369
}
365370

371+
pub struct Output {
372+
pub status: Option<i32>,
373+
pub stdout: Vec<u8>,
374+
pub stderr: Vec<u8>,
375+
}
376+
366377
#[derive(Debug)]
367378
pub struct SanitizedOutput {
368379
pub ok: bool,
@@ -383,7 +394,36 @@ where
383394
cmd
384395
}
385396

386-
pub fn env(config: &Config, cmd: &mut Command) {
397+
pub trait Env {
398+
fn env<K, V>(&mut self, key: K, val: V)
399+
where
400+
K: AsRef<OsStr>,
401+
V: AsRef<OsStr>;
402+
}
403+
404+
impl Env for Command {
405+
fn env<K, V>(&mut self, key: K, val: V)
406+
where
407+
K: AsRef<OsStr>,
408+
V: AsRef<OsStr>,
409+
{
410+
self.env(key, val);
411+
}
412+
}
413+
414+
impl Env for HashMap<String, String> {
415+
fn env<K, V>(&mut self, key: K, val: V)
416+
where
417+
K: AsRef<OsStr>,
418+
V: AsRef<OsStr>,
419+
{
420+
let key = key.as_ref().to_os_string().into_string().unwrap();
421+
let val = val.as_ref().to_os_string().into_string().unwrap();
422+
self.insert(key, val);
423+
}
424+
}
425+
426+
pub fn env<E: Env>(config: &Config, cmd: &mut E) {
387427
// Ensure PATH is prefixed with the rustup-exe directory
388428
let prev_path = env::var_os("PATH");
389429
let mut new_path = config.exedir.clone().into_os_string();
@@ -442,7 +482,111 @@ pub fn cmd_lock() -> &'static RwLock<()> {
442482
&LOCK
443483
}
444484

485+
fn allow_inprocess<I, A>(name: &str, args: I) -> bool
486+
where
487+
I: IntoIterator<Item = A>,
488+
A: AsRef<OsStr>,
489+
{
490+
// Only the rustup alias is currently ready for in-process testing:
491+
// - -init performs self-updates which monkey with global external state.
492+
// - proxies themselves behave appropriately the proxied output needs to be
493+
// collected for assertions to be made on it as our tests traverse layers.
494+
// - self update executions cannot run in-process because on windows the
495+
// process replacement dance would replace the test process.
496+
if name != "rustup" {
497+
return false;
498+
}
499+
let mut is_update = false;
500+
let mut no_self_update = false;
501+
let mut self_cmd = false;
502+
let mut run = false;
503+
for arg in args {
504+
if arg.as_ref() == "update" {
505+
is_update = true;
506+
} else if arg.as_ref() == "--no-self-update" {
507+
no_self_update = true;
508+
} else if arg.as_ref() == "self" {
509+
self_cmd = true;
510+
} else if arg.as_ref() == "run" {
511+
run = true;
512+
}
513+
}
514+
!(run || self_cmd || (is_update && !no_self_update))
515+
}
516+
445517
pub fn run<I, A>(config: &Config, name: &str, args: I, env: &[(&str, &str)]) -> SanitizedOutput
518+
where
519+
I: IntoIterator<Item = A> + Clone,
520+
A: AsRef<OsStr>,
521+
{
522+
let inprocess = allow_inprocess(name, args.clone());
523+
let out = if inprocess {
524+
run_inprocess(config, name, args, env)
525+
} else {
526+
run_subprocess(config, name, args, env)
527+
};
528+
let output = SanitizedOutput {
529+
ok: if let Some(0) = out.status {
530+
true
531+
} else {
532+
false
533+
},
534+
stdout: String::from_utf8(out.stdout).unwrap(),
535+
stderr: String::from_utf8(out.stderr).unwrap(),
536+
};
537+
538+
println!("inprocess: {}", inprocess);
539+
println!("status: {:?}", out.status);
540+
println!("----- stdout\n{}", output.stdout);
541+
println!("----- stderr\n{}", output.stderr);
542+
543+
output
544+
}
545+
546+
pub fn run_inprocess<I, A>(config: &Config, name: &str, args: I, env: &[(&str, &str)]) -> Output
547+
where
548+
I: IntoIterator<Item = A>,
549+
A: AsRef<OsStr>,
550+
{
551+
// should we use vars_os, or skip over non-stringable vars? This is test
552+
// code after all...
553+
let mut vars: HashMap<String, String> = HashMap::default();
554+
self::env(config, &mut vars);
555+
vars.extend(env.iter().map(|(k, v)| (k.to_string(), v.to_string())));
556+
let mut arg_strings: Vec<Box<str>> = Vec::new();
557+
arg_strings.push(name.to_owned().into_boxed_str());
558+
for arg in args {
559+
arg_strings.push(
560+
arg.as_ref()
561+
.to_os_string()
562+
.into_string()
563+
.unwrap()
564+
.into_boxed_str(),
565+
);
566+
}
567+
let tp = Box::new(currentprocess::TestProcess::new(
568+
&*config.workdir.borrow(),
569+
&arg_strings,
570+
vars,
571+
"",
572+
));
573+
let process_res = currentprocess::with(tp.clone(), || rustup_mode::main());
574+
// convert Err's into an ec
575+
let ec = match process_res {
576+
Ok(process_res) => process_res,
577+
Err(e) => {
578+
currentprocess::with(tp.clone(), || rustup::cli::common::report_error(&e));
579+
utils::ExitCode(1)
580+
}
581+
};
582+
Output {
583+
status: Some(ec.0),
584+
stderr: (*tp).get_stderr(),
585+
stdout: (*tp).get_stdout(),
586+
}
587+
}
588+
589+
pub fn run_subprocess<I, A>(config: &Config, name: &str, args: I, env: &[(&str, &str)]) -> Output
446590
where
447591
I: IntoIterator<Item = A>,
448592
A: AsRef<OsStr>,
@@ -452,7 +596,6 @@ where
452596
cmd.env(env.0, env.1);
453597
}
454598

455-
println!("running {:?}", cmd);
456599
let mut retries = 8;
457600
let out = loop {
458601
let lock = cmd_lock().read().unwrap();
@@ -474,18 +617,11 @@ where
474617
}
475618
}
476619
};
477-
478-
let output = SanitizedOutput {
479-
ok: out.status.success(),
480-
stdout: String::from_utf8(out.stdout).unwrap(),
481-
stderr: String::from_utf8(out.stderr).unwrap(),
482-
};
483-
484-
println!("status: {}", out.status);
485-
println!("----- stdout\n{}", output.stdout);
486-
println!("----- stderr\n{}", output.stderr);
487-
488-
output
620+
Output {
621+
status: out.status.code(),
622+
stdout: out.stdout,
623+
stderr: out.stderr,
624+
}
489625
}
490626

491627
#[derive(Copy, Clone, Eq, PartialEq)]

0 commit comments

Comments
 (0)