diff --git a/Cargo.lock b/Cargo.lock index c34dd3ba18..8f026590e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,7 @@ dependencies = [ "scopeguard 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", @@ -600,6 +601,7 @@ dependencies = [ "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wait-timeout 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winreg 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -727,6 +729,18 @@ dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempfile" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "term" version = "0.4.4" @@ -866,6 +880,14 @@ name = "vec_map" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wait-timeout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "walkdir" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index d876bd09e2..cb7f60c987 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ term = "0.4.4" itertools = "0.4.1" time = "0.1.34" tempdir = "0.3.4" +tempfile = "2.1.4" libc = "0.2.0" rand = "0.3.11" scopeguard = "0.1.2" diff --git a/src/rustup-mock/Cargo.toml b/src/rustup-mock/Cargo.toml index 974092ef87..ddaddc52b6 100644 --- a/src/rustup-mock/Cargo.toml +++ b/src/rustup-mock/Cargo.toml @@ -21,6 +21,7 @@ tar = "0.4.0" toml = "0.1.27" rustup-utils = { path = "../rustup-utils", version = "0.5.0" } sha2 = "0.1.2" +wait-timeout = "0.1.3" [target."cfg(windows)".dependencies] winapi = "0.2.8" diff --git a/src/rustup-mock/src/clitools.rs b/src/rustup-mock/src/clitools.rs index 3074db01dd..469c8f99b9 100644 --- a/src/rustup-mock/src/clitools.rs +++ b/src/rustup-mock/src/clitools.rs @@ -3,11 +3,12 @@ use std::path::{PathBuf, Path}; use std::env; -use std::process::Command; +use std::process::{Command, Stdio}; use std::env::consts::EXE_SUFFIX; use std::fs::{self, File}; use std::io::{self, Read, Write}; use std::sync::Mutex; +use std::time::Duration; use tempdir::TempDir; use {MockInstallerBuilder, MockCommand}; use dist::{MockDistServer, MockChannel, MockPackage, @@ -15,6 +16,7 @@ use dist::{MockDistServer, MockChannel, MockPackage, ManifestVersion}; use url::Url; use scopeguard; +use wait_timeout::ChildExt; /// The configuration used by the tests in this module pub struct Config { @@ -218,6 +220,24 @@ pub fn expect_err_ex(config: &Config, args: &[&str], assert!(out.stderr == stderr, format!("err {:?}", args)); } +pub fn expect_timeout_ok(config: &Config, timeout: Duration, args: &[&str]) { + let mut child = cmd(config, args[0], &args[1..]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn().unwrap(); + + match child.wait_timeout(timeout).unwrap() { + Some(status) => { + assert!(status.success(), "not ok {:?}", args); + } + None => { + // child hasn't exited yet + child.kill().unwrap(); + panic!("command timed out: {:?}", args); + } + } +} + #[derive(Debug)] pub struct SanitizedOutput { pub ok: bool, @@ -545,18 +565,10 @@ fn mock_bin(_name: &str, version: &str, version_hash: &str) -> Vec { let ref dest_path = tempdir.path().join(&format!("out{}", EXE_SUFFIX)); // Write the source - let ref source = format!(r#" - fn main() {{ - let args: Vec<_> = ::std::env::args().collect(); - if args.get(1) == Some(&"--version".to_string()) {{ - println!("{} ({})"); - }} else if args.get(1) == Some(&"--empty-arg-test".to_string()) {{ - assert!(args.get(2) == Some(&"".to_string())); - }} else {{ - panic!("bad mock proxy commandline"); - }} - }} - "#, EXAMPLE_VERSION, EXAMPLE_VERSION_HASH); + let source = include_str!("mock_bin_src.rs") + .replace("%EXAMPLE_VERSION%", EXAMPLE_VERSION) + .replace("%EXAMPLE_VERSION_HASH%", EXAMPLE_VERSION_HASH); + File::create(source_path).and_then(|mut f| f.write_all(source.as_bytes())).unwrap(); // Create the executable diff --git a/src/rustup-mock/src/lib.rs b/src/rustup-mock/src/lib.rs index 3b55a0ddca..cf2fe3914b 100644 --- a/src/rustup-mock/src/lib.rs +++ b/src/rustup-mock/src/lib.rs @@ -12,6 +12,7 @@ extern crate tar; extern crate toml; extern crate rustup_utils; extern crate sha2; +extern crate wait_timeout; #[cfg(windows)] extern crate winapi; diff --git a/src/rustup-mock/src/mock_bin_src.rs b/src/rustup-mock/src/mock_bin_src.rs new file mode 100644 index 0000000000..d4728f5485 --- /dev/null +++ b/src/rustup-mock/src/mock_bin_src.rs @@ -0,0 +1,19 @@ +use std::io::{self, BufWriter, Write}; + +fn main() { + let args: Vec<_> = ::std::env::args().collect(); + if args.get(1) == Some(&"--version".to_string()) { + println!("%EXAMPLE_VERSION% (%EXAMPLE_VERSION_HASH%)"); + } else if args.get(1) == Some(&"--empty-arg-test".to_string()) { + assert!(args.get(2) == Some(&"".to_string())); + } else if args.get(1) == Some(&"--huge-output".to_string()) { + let out = io::stderr(); + let lock = out.lock(); + let mut buf = BufWriter::new(lock); + for _ in 0 .. 10000 { + buf.write_all(b"error: a value named `fail` has already been defined in this module [E0428]\n").unwrap(); + } + } else { + panic!("bad mock proxy commandline"); + } +} diff --git a/src/rustup/command.rs b/src/rustup/command.rs index 44492150ef..52119a2687 100644 --- a/src/rustup/command.rs +++ b/src/rustup/command.rs @@ -1,10 +1,12 @@ use std::env; use std::ffi::OsStr; -use std::io::{self, Write, BufRead, BufReader}; +use std::fs::File; +use std::io::{self, Write, BufRead, BufReader, Seek, SeekFrom}; use std::path::PathBuf; use std::process::{self, Command, Stdio}; use std::time::Instant; use regex::Regex; +use tempfile::tempfile; use Cfg; use errors::*; @@ -29,6 +31,18 @@ pub fn run_command_for_dir>(cmd: Command, } fn telemetry_rustc>(mut cmd: Command, args: &[S], cfg: &Cfg) -> Result<()> { + #[cfg(unix)] + fn file_as_stdio(file: &File) -> Stdio { + use std::os::unix::io::{AsRawFd, FromRawFd}; + unsafe { Stdio::from_raw_fd(file.as_raw_fd()) } + } + + #[cfg(windows)] + fn file_as_stdio(file: &File) -> Stdio { + use std::os::windows::io::{AsRawHandle, FromRawHandle}; + unsafe { Stdio::from_raw_handle(file.as_raw_handle()) } + } + let now = Instant::now(); cmd.args(&args[1..]); @@ -44,15 +58,17 @@ fn telemetry_rustc>(mut cmd: Command, args: &[S], cfg: &Cfg) -> cmd.arg("always"); } + let mut cmd_err_file = tempfile().unwrap(); + let cmd_err_stdio = file_as_stdio(&cmd_err_file); + // FIXME rust-lang/rust#32254. It's not clear to me // when and why this is needed. let mut cmd = cmd.stdin(Stdio::inherit()) .stdout(Stdio::inherit()) - .stderr(Stdio::piped()) + .stderr(cmd_err_stdio) .spawn() .unwrap(); - let mut buffered_stderr = BufReader::new(cmd.stderr.take().unwrap()); let status = cmd.wait(); let duration = now.elapsed(); @@ -75,6 +91,10 @@ fn telemetry_rustc>(mut cmd: Command, args: &[S], cfg: &Cfg) -> let stderr = io::stderr(); let mut handle = stderr.lock(); + cmd_err_file.seek(SeekFrom::Start(0)).unwrap(); + + let mut buffered_stderr = BufReader::new(cmd_err_file); + while buffered_stderr.read_line(&mut buffer).unwrap() > 0 { let b = buffer.to_owned(); buffer.clear(); diff --git a/src/rustup/lib.rs b/src/rustup/lib.rs index 0501b2887e..f3dda44699 100644 --- a/src/rustup/lib.rs +++ b/src/rustup/lib.rs @@ -9,6 +9,7 @@ extern crate url; extern crate regex; extern crate itertools; extern crate rustc_serialize; +extern crate tempfile; extern crate time; extern crate toml; #[cfg(unix)] diff --git a/tests/cli-misc.rs b/tests/cli-misc.rs index a5bce27b13..ce536f4f9f 100644 --- a/tests/cli-misc.rs +++ b/tests/cli-misc.rs @@ -7,13 +7,14 @@ extern crate rustup_mock; extern crate time; use rustup_mock::clitools::{self, Config, Scenario, expect_stdout_ok, expect_stderr_ok, - expect_ok, expect_err, run, - this_host_triple}; + expect_ok, expect_err, expect_timeout_ok, + run, this_host_triple}; use rustup_utils::{raw, utils}; use time::Duration; use std::ops::Sub; use std::ops::Add; +use std::time::Duration as StdDuration; macro_rules! for_host { ($s: expr) => (&format!($s, this_host_triple())) } @@ -334,6 +335,16 @@ fn enabling_telemetry_and_compiling_creates_log() { }); } +#[test] +fn telemetry_supports_huge_output() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "stable"]); + expect_ok(config, &["rustup", "telemetry", "enable"]); + expect_timeout_ok(&config, StdDuration::from_secs(5), &["rustc", "--huge-output"]); + expect_stdout_ok(config, &["rustup", "telemetry", "analyze"], "'E0428': 10000") + }) +} + #[test] fn telemetry_cleanup_removes_old_files() { setup(&|config| {