diff --git a/src/libtest/formatters.rs b/src/libtest/formatters.rs new file mode 100644 index 0000000000000..47ed6742da94e --- /dev/null +++ b/src/libtest/formatters.rs @@ -0,0 +1,380 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::*; + +pub(crate) trait OutputFormatter { + fn write_run_start(&mut self, len: usize) -> io::Result<()>; + fn write_test_start(&mut self, + test: &TestDesc, + align: NamePadding, + max_name_len: usize) -> io::Result<()>; + fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()>; + fn write_result(&mut self, desc: &TestDesc, result: &TestResult) -> io::Result<()>; + fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result; +} + +pub(crate) struct HumanFormatter { + out: OutputLocation, + terse: bool, + use_color: bool, + test_count: usize, +} + +impl HumanFormatter { + pub fn new(out: OutputLocation, use_color: bool, terse: bool) -> Self { + HumanFormatter { + out, + terse, + use_color, + test_count: 0, + } + } + + #[cfg(test)] + pub fn output_location(&self) -> &OutputLocation { + &self.out + } + + pub fn write_ok(&mut self) -> io::Result<()> { + self.write_short_result("ok", ".", term::color::GREEN) + } + + pub fn write_failed(&mut self) -> io::Result<()> { + self.write_short_result("FAILED", "F", term::color::RED) + } + + pub fn write_ignored(&mut self) -> io::Result<()> { + self.write_short_result("ignored", "i", term::color::YELLOW) + } + + pub fn write_allowed_fail(&mut self) -> io::Result<()> { + self.write_short_result("FAILED (allowed)", "a", term::color::YELLOW) + } + + pub fn write_metric(&mut self) -> io::Result<()> { + self.write_pretty("metric", term::color::CYAN) + } + + pub fn write_bench(&mut self) -> io::Result<()> { + self.write_pretty("bench", term::color::CYAN) + } + + pub fn write_short_result(&mut self, verbose: &str, quiet: &str, color: term::color::Color) + -> io::Result<()> { + if self.terse { + self.write_pretty(quiet, color)?; + if self.test_count % QUIET_MODE_MAX_COLUMN == QUIET_MODE_MAX_COLUMN - 1 { + // we insert a new line every 100 dots in order to flush the + // screen when dealing with line-buffered output (e.g. piping to + // `stamp` in the rust CI). + self.write_plain("\n")?; + } + + self.test_count += 1; + Ok(()) + } else { + self.write_pretty(verbose, color)?; + self.write_plain("\n") + } + } + + pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> { + match self.out { + Pretty(ref mut term) => { + if self.use_color { + term.fg(color)?; + } + term.write_all(word.as_bytes())?; + if self.use_color { + term.reset()?; + } + term.flush() + } + Raw(ref mut stdout) => { + stdout.write_all(word.as_bytes())?; + stdout.flush() + } + } + } + + pub fn write_plain>(&mut self, s: S) -> io::Result<()> { + let s = s.as_ref(); + self.out.write_all(s.as_bytes())?; + self.out.flush() + } + + pub fn write_outputs(&mut self, state: &ConsoleTestState) -> io::Result<()> { + self.write_plain("\nsuccesses:\n")?; + let mut successes = Vec::new(); + let mut stdouts = String::new(); + for &(ref f, ref stdout) in &state.not_failures { + successes.push(f.name.to_string()); + if !stdout.is_empty() { + stdouts.push_str(&format!("---- {} stdout ----\n\t", f.name)); + let output = String::from_utf8_lossy(stdout); + stdouts.push_str(&output); + stdouts.push_str("\n"); + } + } + if !stdouts.is_empty() { + self.write_plain("\n")?; + self.write_plain(&stdouts)?; + } + + self.write_plain("\nsuccesses:\n")?; + successes.sort(); + for name in &successes { + self.write_plain(&format!(" {}\n", name))?; + } + Ok(()) + } + + pub fn write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> { + self.write_plain("\nfailures:\n")?; + let mut failures = Vec::new(); + let mut fail_out = String::new(); + for &(ref f, ref stdout) in &state.failures { + failures.push(f.name.to_string()); + if !stdout.is_empty() { + fail_out.push_str(&format!("---- {} stdout ----\n\t", f.name)); + let output = String::from_utf8_lossy(stdout); + fail_out.push_str(&output); + fail_out.push_str("\n"); + } + } + if !fail_out.is_empty() { + self.write_plain("\n")?; + self.write_plain(&fail_out)?; + } + + self.write_plain("\nfailures:\n")?; + failures.sort(); + for name in &failures { + self.write_plain(&format!(" {}\n", name))?; + } + Ok(()) + } +} + +impl OutputFormatter for HumanFormatter { + fn write_run_start(&mut self, len: usize) -> io::Result<()> { + let noun = if len != 1 { + "tests" + } else { + "test" + }; + self.write_plain(&format!("\nrunning {} {}\n", len, noun)) + } + + fn write_test_start(&mut self, + test: &TestDesc, + align: NamePadding, + max_name_len: usize) -> io::Result<()> { + if self.terse && align != PadOnRight { + Ok(()) + } + else { + let name = test.padded_name(max_name_len, align); + self.write_plain(&format!("test {} ... ", name)) + } + } + + fn write_result(&mut self, _desc: &TestDesc, result: &TestResult) -> io::Result<()> { + match *result { + TrOk => self.write_ok(), + TrFailed | TrFailedMsg(_) => self.write_failed(), + TrIgnored => self.write_ignored(), + TrAllowedFail => self.write_allowed_fail(), + TrMetrics(ref mm) => { + self.write_metric()?; + self.write_plain(&format!(": {}\n", mm.fmt_metrics())) + } + TrBench(ref bs) => { + self.write_bench()?; + self.write_plain(&format!(": {}\n", fmt_bench_samples(bs))) + } + } + } + + fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { + self.write_plain(&format!("test {} has been running for over {} seconds\n", + desc.name, + TEST_WARN_TIMEOUT_S)) + } + + fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result { + if state.options.display_output { + self.write_outputs(state)?; + } + let success = state.failed == 0; + if !success { + self.write_failures(state)?; + } + + self.write_plain("\ntest result: ")?; + + if success { + // There's no parallelism at this point so it's safe to use color + self.write_pretty("ok", term::color::GREEN)?; + } else { + self.write_pretty("FAILED", term::color::RED)?; + } + + let s = if state.allowed_fail > 0 { + format!( + ". {} passed; {} failed ({} allowed); {} ignored; {} measured; {} filtered out\n\n", + state.passed, + state.failed + state.allowed_fail, + state.allowed_fail, + state.ignored, + state.measured, + state.filtered_out) + } else { + format!( + ". {} passed; {} failed; {} ignored; {} measured; {} filtered out\n\n", + state.passed, + state.failed, + state.ignored, + state.measured, + state.filtered_out) + }; + + self.write_plain(&s)?; + + Ok(success) + } +} + +pub(crate) struct JsonFormatter { + out: OutputLocation, + had_events: bool +} + +impl JsonFormatter { + pub fn new(out: OutputLocation) -> Self { + Self { + out, + had_events: false + } + } + + fn write_str>(&mut self, s: S) -> io::Result<()> { + self.out.write_all(s.as_ref().as_ref()) + } + + fn write_event(&mut self, event: &str) -> io::Result<()> { + if self.had_events { + self.out.write_all(b",\n")?; + } + else { + self.had_events = true; + } + + self.out.write_all(event.as_ref()) + } +} + +impl OutputFormatter for JsonFormatter { + fn write_run_start(&mut self, _len: usize) -> io::Result<()> { + self.write_str("{\n\tevents: [\n") + } + + fn write_test_start(&mut self, + desc: &TestDesc, + _align: NamePadding, + _max_name_len: usize) -> io::Result<()> { + self.write_event(&*format!("\t\t{{ \"test\": \"{}\", \"event\": \"started\" }}", desc.name)) + } + + fn write_result(&mut self, desc: &TestDesc, result: &TestResult) -> io::Result<()> { + let output = match *result { + TrOk => { + format!("\t\t{{ \"test\": \"{}\", \"event\": \"ok\" }}", desc.name) + }, + + TrFailed => { + format!("\t\t{{ \"test\": \"{}\", \"event\": \"failed\" }}", desc.name) + }, + + TrFailedMsg(ref m) => { + format!("\t\t{{ \"test\": \"{}\", \"event\": \"failed\", \"extra\": \"{}\" }}", + desc.name, + m) + }, + + TrIgnored => { + format!("\t\t{{ \"test\": \"{}\", \"event\": \"ignored\" }}", desc.name) + }, + + TrAllowedFail => { + format!("\t\t{{ \"test\": \"{}\", \"event\": \"allowed_failure\" }}", desc.name) + }, + + TrMetrics(ref mm) => { + format!("\t\t{{ \"test\": \"{}\", \"event\": \"metrics\", \"extra\": \"{}\" }}", + desc.name, + mm.fmt_metrics()) + }, + + TrBench(ref bs) => { + format!("\t\t{{ \"test\": \"{}\", \"event\": \"bench\", \"extra\": \"{}\" }}", + desc.name, + fmt_bench_samples(bs)) + }, + }; + + self.write_event(&*output) + } + + fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { + self.write_event(&*format!("\t{{ \"test\": \"{}\", \"event\": \"timeout\" }}", desc.name)) + } + + fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result { + self.write_str("\n\t],\n\t\"summary\": {\n")?; + + self.write_str(&*format!("\t\t\"passed\": {},\n", state.passed))?; + self.write_str(&*format!("\t\t\"failed\": {},\n", state.failed + state.allowed_fail))?; + self.write_str(&*format!("\t\t\"allowed_fail\": {},\n", state.allowed_fail))?; + self.write_str(&*format!("\t\t\"ignored\": {},\n", state.ignored))?; + self.write_str(&*format!("\t\t\"measured\": {},\n", state.measured))?; + + if state.failed == 0 { + self.write_str(&*format!("\t\t\"filtered_out\": {}\n", state.filtered_out))?; + } else { + self.write_str(&*format!("\t\t\"filtered_out\": {},\n", state.filtered_out))?; + self.write_str("\t\t\"failures\": [\n")?; + + let mut has_items = false; + for &(ref f, ref stdout) in &state.failures { + if !stdout.is_empty() { + if has_items { + self.write_str(",\n")?; + } else { + has_items = true; + } + + let output = String::from_utf8_lossy(stdout) + .replace("\\", "\\\\") + .replace("\"", "\\\""); + + self.write_str(&*format!("\t\t\t\"{}\": \"{}\"", f.name, output))?; + } + } + + self.write_str("\n\t\t]\n")?; + } + + self.write_str("\t}\n}\n")?; + + Ok(state.failed == 0) + } +} diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 76abcb83edc53..5d4a3017a2fd1 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -82,6 +82,9 @@ pub mod test { } pub mod stats; +mod formatters; + +use formatters::*; // The name of a test. By convention this follows the rules for rust // paths; i.e. it should be a series of identifiers separated by double @@ -334,6 +337,13 @@ pub enum ColorConfig { NeverColor, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum OutputFormat { + Pretty, + Terse, + Json +} + #[derive(Debug)] pub struct TestOpts { pub list: bool, @@ -345,7 +355,7 @@ pub struct TestOpts { pub logfile: Option, pub nocapture: bool, pub color: ColorConfig, - pub quiet: bool, + pub format: OutputFormat, pub test_threads: Option, pub skip: Vec, pub options: Options, @@ -364,7 +374,7 @@ impl TestOpts { logfile: None, nocapture: false, color: AutoColor, - quiet: false, + format: OutputFormat::Pretty, test_threads: None, skip: vec![], options: Options::new(), @@ -390,12 +400,17 @@ fn optgroups() -> getopts::Options { in parallel", "n_threads") .optmulti("", "skip", "Skip tests whose names contain FILTER (this flag can \ be used multiple times)","FILTER") - .optflag("q", "quiet", "Display one character per test instead of one line") + .optflag("q", "quiet", "Display one character per test instead of one line. \ + Alias to --format=terse") .optflag("", "exact", "Exactly match filters rather than by substring") .optopt("", "color", "Configure coloring of output: auto = colorize if stdout is a tty and tests are run on serially (default); always = always colorize output; - never = never colorize output;", "auto|always|never"); + never = never colorize output;", "auto|always|never") + .optopt("", "format", "Configure formatting of output: + pretty = Print verbose output; + terse = Display one character per test; + json = Output a json document", "pretty|terse|json"); return opts } @@ -495,6 +510,19 @@ pub fn parse_opts(args: &[String]) -> Option { } }; + let format = match matches.opt_str("format").as_ref().map(|s| &**s) { + None if quiet => OutputFormat::Terse, + Some("pretty") | None => OutputFormat::Pretty, + Some("terse") => OutputFormat::Terse, + Some("json") => OutputFormat::Json, + + Some(v) => { + return Some(Err(format!("argument for --format must be pretty, terse, or json (was \ + {})", + v))) + } + }; + let test_opts = TestOpts { list, filter, @@ -505,7 +533,7 @@ pub fn parse_opts(args: &[String]) -> Option { logfile, nocapture, color, - quiet, + format, test_threads, skip: matches.opt_strs("skip"), options: Options::new(), @@ -538,11 +566,24 @@ enum OutputLocation { Raw(T), } -struct ConsoleTestState { +impl Write for OutputLocation { + fn write(&mut self, buf: &[u8]) -> io::Result { + match *self { + Pretty(ref mut term) => term.write(buf), + Raw(ref mut stdout) => stdout.write(buf) + } + } + + fn flush(&mut self) -> io::Result<()> { + match *self { + Pretty(ref mut term) => term.flush(), + Raw(ref mut stdout) => stdout.flush() + } + } +} + +struct ConsoleTestState { log_out: Option, - out: OutputLocation, - use_color: bool, - quiet: bool, total: usize, passed: usize, failed: usize, @@ -557,22 +598,15 @@ struct ConsoleTestState { options: Options, } -impl ConsoleTestState { - pub fn new(opts: &TestOpts, _: Option) -> io::Result> { +impl ConsoleTestState { + pub fn new(opts: &TestOpts) -> io::Result { let log_out = match opts.logfile { Some(ref path) => Some(File::create(path)?), None => None, }; - let out = match term::stdout() { - None => Raw(io::stdout()), - Some(t) => Pretty(t), - }; Ok(ConsoleTestState { - out, log_out, - use_color: use_color(opts), - quiet: opts.quiet, total: 0, passed: 0, failed: 0, @@ -588,122 +622,6 @@ impl ConsoleTestState { }) } - pub fn write_ok(&mut self) -> io::Result<()> { - self.write_short_result("ok", ".", term::color::GREEN) - } - - pub fn write_failed(&mut self) -> io::Result<()> { - self.write_short_result("FAILED", "F", term::color::RED) - } - - pub fn write_ignored(&mut self) -> io::Result<()> { - self.write_short_result("ignored", "i", term::color::YELLOW) - } - - pub fn write_allowed_fail(&mut self) -> io::Result<()> { - self.write_short_result("FAILED (allowed)", "a", term::color::YELLOW) - } - - pub fn write_metric(&mut self) -> io::Result<()> { - self.write_pretty("metric", term::color::CYAN) - } - - pub fn write_bench(&mut self) -> io::Result<()> { - self.write_pretty("bench", term::color::CYAN) - } - - pub fn write_short_result(&mut self, verbose: &str, quiet: &str, color: term::color::Color) - -> io::Result<()> { - if self.quiet { - self.write_pretty(quiet, color)?; - if self.current_test_count() % QUIET_MODE_MAX_COLUMN == QUIET_MODE_MAX_COLUMN - 1 { - // we insert a new line every 100 dots in order to flush the - // screen when dealing with line-buffered output (e.g. piping to - // `stamp` in the rust CI). - self.write_plain("\n")?; - } - Ok(()) - } else { - self.write_pretty(verbose, color)?; - self.write_plain("\n") - } - } - - pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> { - match self.out { - Pretty(ref mut term) => { - if self.use_color { - term.fg(color)?; - } - term.write_all(word.as_bytes())?; - if self.use_color { - term.reset()?; - } - term.flush() - } - Raw(ref mut stdout) => { - stdout.write_all(word.as_bytes())?; - stdout.flush() - } - } - } - - pub fn write_plain>(&mut self, s: S) -> io::Result<()> { - let s = s.as_ref(); - match self.out { - Pretty(ref mut term) => { - term.write_all(s.as_bytes())?; - term.flush() - } - Raw(ref mut stdout) => { - stdout.write_all(s.as_bytes())?; - stdout.flush() - } - } - } - - pub fn write_run_start(&mut self, len: usize) -> io::Result<()> { - self.total = len; - let noun = if len != 1 { - "tests" - } else { - "test" - }; - self.write_plain(&format!("\nrunning {} {}\n", len, noun)) - } - - pub fn write_test_start(&mut self, test: &TestDesc, align: NamePadding) -> io::Result<()> { - if self.quiet && align != PadOnRight { - Ok(()) - } else { - let name = test.padded_name(self.max_name_len, align); - self.write_plain(&format!("test {} ... ", name)) - } - } - - pub fn write_result(&mut self, result: &TestResult) -> io::Result<()> { - match *result { - TrOk => self.write_ok(), - TrFailed | TrFailedMsg(_) => self.write_failed(), - TrIgnored => self.write_ignored(), - TrAllowedFail => self.write_allowed_fail(), - TrMetrics(ref mm) => { - self.write_metric()?; - self.write_plain(&format!(": {}\n", mm.fmt_metrics())) - } - TrBench(ref bs) => { - self.write_bench()?; - self.write_plain(&format!(": {}\n", fmt_bench_samples(bs))) - } - } - } - - pub fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { - self.write_plain(&format!("test {} has been running for over {} seconds\n", - desc.name, - TEST_WARN_TIMEOUT_S)) - } - pub fn write_log>(&mut self, msg: S) -> io::Result<()> { let msg = msg.as_ref(); match self.log_out { @@ -727,101 +645,9 @@ impl ConsoleTestState { test.name)) } - pub fn write_failures(&mut self) -> io::Result<()> { - self.write_plain("\nfailures:\n")?; - let mut failures = Vec::new(); - let mut fail_out = String::new(); - for &(ref f, ref stdout) in &self.failures { - failures.push(f.name.to_string()); - if !stdout.is_empty() { - fail_out.push_str(&format!("---- {} stdout ----\n\t", f.name)); - let output = String::from_utf8_lossy(stdout); - fail_out.push_str(&output); - fail_out.push_str("\n"); - } - } - if !fail_out.is_empty() { - self.write_plain("\n")?; - self.write_plain(&fail_out)?; - } - - self.write_plain("\nfailures:\n")?; - failures.sort(); - for name in &failures { - self.write_plain(&format!(" {}\n", name))?; - } - Ok(()) - } - - pub fn write_outputs(&mut self) -> io::Result<()> { - self.write_plain("\nsuccesses:\n")?; - let mut successes = Vec::new(); - let mut stdouts = String::new(); - for &(ref f, ref stdout) in &self.not_failures { - successes.push(f.name.to_string()); - if !stdout.is_empty() { - stdouts.push_str(&format!("---- {} stdout ----\n\t", f.name)); - let output = String::from_utf8_lossy(stdout); - stdouts.push_str(&output); - stdouts.push_str("\n"); - } - } - if !stdouts.is_empty() { - self.write_plain("\n")?; - self.write_plain(&stdouts)?; - } - - self.write_plain("\nsuccesses:\n")?; - successes.sort(); - for name in &successes { - self.write_plain(&format!(" {}\n", name))?; - } - Ok(()) - } - fn current_test_count(&self) -> usize { self.passed + self.failed + self.ignored + self.measured + self.allowed_fail } - - pub fn write_run_finish(&mut self) -> io::Result { - assert!(self.current_test_count() == self.total); - - if self.options.display_output { - self.write_outputs()?; - } - let success = self.failed == 0; - if !success { - self.write_failures()?; - } - - self.write_plain("\ntest result: ")?; - if success { - // There's no parallelism at this point so it's safe to use color - self.write_pretty("ok", term::color::GREEN)?; - } else { - self.write_pretty("FAILED", term::color::RED)?; - } - let s = if self.allowed_fail > 0 { - format!( - ". {} passed; {} failed ({} allowed); {} ignored; {} measured; {} filtered out\n\n", - self.passed, - self.failed + self.allowed_fail, - self.allowed_fail, - self.ignored, - self.measured, - self.filtered_out) - } else { - format!( - ". {} passed; {} failed; {} ignored; {} measured; {} filtered out\n\n", - self.passed, - self.failed, - self.ignored, - self.measured, - self.filtered_out) - }; - self.write_plain(&s)?; - return Ok(success); - } } // Format a number with thousands separators @@ -867,7 +693,14 @@ pub fn fmt_bench_samples(bs: &BenchSamples) -> String { // List the tests to console, and optionally to logfile. Filters are honored. pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Result<()> { - let mut st = ConsoleTestState::new(opts, None::)?; + let output = match term::stdout() { + None => Raw(io::stdout()), + Some(t) => Pretty(t), + }; + + let quiet = opts.format == OutputFormat::Terse; + let mut out = HumanFormatter::new(output, use_color(opts), quiet); + let mut st = ConsoleTestState::new(opts)?; let mut ntest = 0; let mut nbench = 0; @@ -884,7 +717,7 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Res StaticMetricFn(..) | DynMetricFn(..) => { nmetric += 1; "metric" }, }; - st.write_plain(format!("{}: {}\n", name, fntype))?; + out.write_plain(format!("{}: {}\n", name, fntype))?; st.write_log(format!("{} {}\n", fntype, name))?; } @@ -895,11 +728,11 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Res } } - if !opts.quiet { + if !quiet { if ntest != 0 || nbench != 0 || nmetric != 0 { - st.write_plain("\n")?; + out.write_plain("\n")?; } - st.write_plain(format!("{}, {}, {}\n", + out.write_plain(format!("{}, {}, {}\n", plural(ntest, "test"), plural(nbench, "benchmark"), plural(nmetric, "metric")))?; @@ -911,15 +744,21 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Res // A simple console test runner pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Result { - fn callback(event: &TestEvent, st: &mut ConsoleTestState) -> io::Result<()> { + fn callback(event: &TestEvent, + st: &mut ConsoleTestState, + out: &mut OutputFormatter) -> io::Result<()> { + match (*event).clone() { - TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()), + TeFiltered(ref filtered_tests) => { + st.total = filtered_tests.len(); + out.write_run_start(filtered_tests.len()) + }, TeFilteredOut(filtered_out) => Ok(st.filtered_out = filtered_out), - TeWait(ref test, padding) => st.write_test_start(test, padding), - TeTimeout(ref test) => st.write_timeout(test), + TeWait(ref test, padding) => out.write_test_start(test, padding, st.max_name_len), + TeTimeout(ref test) => out.write_timeout(test), TeResult(test, result, stdout) => { st.write_log_result(&test, &result)?; - st.write_result(&result)?; + out.write_result(&test, &result)?; match result { TrOk => { st.passed += 1; @@ -960,7 +799,17 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu } } - let mut st = ConsoleTestState::new(opts, None::)?; + let output = match term::stdout() { + None => Raw(io::stdout()), + Some(t) => Pretty(t), + }; + + let mut out: Box = match opts.format { + OutputFormat::Pretty => Box::new(HumanFormatter::new(output, use_color(opts), false)), + OutputFormat::Terse => Box::new(HumanFormatter::new(output, use_color(opts), true)), + OutputFormat::Json => Box::new(JsonFormatter::new(output)), + }; + let mut st = ConsoleTestState::new(opts)?; fn len_if_padded(t: &TestDescAndFn) -> usize { match t.testfn.padding() { PadNone => 0, @@ -971,8 +820,11 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu let n = t.desc.name.as_slice(); st.max_name_len = n.len(); } - run_tests(opts, tests, |x| callback(&x, &mut st))?; - return st.write_run_finish(); + run_tests(opts, tests, |x| callback(&x, &mut st, &mut *out))?; + + assert!(st.current_test_count() == st.total); + + return out.write_run_finish(&st); } #[test] @@ -991,11 +843,10 @@ fn should_sort_failures_before_printing_them() { allow_fail: false, }; - let mut st = ConsoleTestState { + let mut out = HumanFormatter::new(Raw(Vec::new()), false, false); + + let st = ConsoleTestState { log_out: None, - out: Raw(Vec::new()), - use_color: false, - quiet: false, total: 0, passed: 0, failed: 0, @@ -1010,10 +861,10 @@ fn should_sort_failures_before_printing_them() { not_failures: Vec::new(), }; - st.write_failures().unwrap(); - let s = match st.out { - Raw(ref m) => String::from_utf8_lossy(&m[..]), - Pretty(_) => unreachable!(), + out.write_failures(&st).unwrap(); + let s = match out.output_location() { + &Raw(ref m) => String::from_utf8_lossy(&m[..]), + &Pretty(_) => unreachable!(), }; let apos = s.find("a").unwrap(); diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 9fb6a3f5e0753..15cdd0283fde1 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -340,7 +340,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts { filter: config.filter.clone(), filter_exact: config.filter_exact, run_ignored: config.run_ignored, - quiet: config.quiet, + format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty }, logfile: config.logfile.clone(), run_tests: true, bench_benchmarks: true,