Skip to content

Commit 5af700c

Browse files
committed
Allow capturing of a task's stdout and log
The major use case for this is the test harness. This allows suppression of all failure messages of #[should_fail] tests. Successful test runs will now have no extra output other than what the test harness normally prints.
1 parent 61443dc commit 5af700c

File tree

8 files changed

+227
-118
lines changed

8 files changed

+227
-118
lines changed

src/libextra/test.rs

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use std::io;
3434
use std::io::File;
3535
use std::io::Writer;
3636
use std::io::stdio::StdWriter;
37+
use std::str;
3738
use std::task;
3839
use std::to_str::ToStr;
3940
use std::f64;
@@ -350,7 +351,7 @@ struct ConsoleTestState<T> {
350351
ignored: uint,
351352
measured: uint,
352353
metrics: MetricMap,
353-
failures: ~[TestDesc],
354+
failures: ~[(TestDesc, ~[u8])],
354355
max_name_len: uint, // number of columns to fill when aligning names
355356
}
356357

@@ -484,14 +485,27 @@ impl<T: Writer> ConsoleTestState<T> {
484485
}
485486
486487
pub fn write_failures(&mut self) {
487-
self.write_plain("\nfailures:\n");
488488
let mut failures = ~[];
489-
for f in self.failures.iter() {
489+
let mut fail_out = ~"";
490+
for &(ref f, ref stdout) in self.failures.iter() {
490491
failures.push(f.name.to_str());
492+
if stdout.len() > 0 {
493+
fail_out.push_str(format!("---- {} stdout ----\n\t",
494+
f.name.to_str()));
495+
let output = str::from_utf8(*stdout);
496+
fail_out.push_str(output.replace("\n", "\n\t"));
497+
fail_out.push_str("\n");
498+
}
499+
}
500+
if fail_out.len() > 0 {
501+
self.write_plain("\n");
502+
self.write_plain(fail_out);
491503
}
504+
505+
self.write_plain("\nfailures:\n");
492506
sort::tim_sort(failures);
493507
for name in failures.iter() {
494-
self.write_plain(format!(" {}\n", name.to_str()));
508+
self.write_plain(format!(" {}\n", name.as_slice()));
495509
}
496510
}
497511

@@ -612,7 +626,7 @@ pub fn run_tests_console(opts: &TestOpts,
612626
match (*event).clone() {
613627
TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
614628
TeWait(ref test, padding) => st.write_test_start(test, padding),
615-
TeResult(test, result) => {
629+
TeResult(test, result, stdout) => {
616630
st.write_log(&test, &result);
617631
st.write_result(&result);
618632
match result {
@@ -634,7 +648,7 @@ pub fn run_tests_console(opts: &TestOpts,
634648
}
635649
TrFailed => {
636650
st.failed += 1;
637-
st.failures.push(test);
651+
st.failures.push((test, stdout));
638652
}
639653
}
640654
}
@@ -696,7 +710,7 @@ fn should_sort_failures_before_printing_them() {
696710
measured: 0u,
697711
max_name_len: 10u,
698712
metrics: MetricMap::new(),
699-
failures: ~[test_b, test_a]
713+
failures: ~[(test_b, ~[]), (test_a, ~[])]
700714
};
701715

702716
st.write_failures();
@@ -716,10 +730,10 @@ fn use_color() -> bool { return get_concurrency() == 1; }
716730
enum TestEvent {
717731
TeFiltered(~[TestDesc]),
718732
TeWait(TestDesc, NamePadding),
719-
TeResult(TestDesc, TestResult),
733+
TeResult(TestDesc, TestResult, ~[u8] /* stdout */),
720734
}
721735

722-
type MonitorMsg = (TestDesc, TestResult);
736+
type MonitorMsg = (TestDesc, TestResult, ~[u8] /* stdout */);
723737

724738
fn run_tests(opts: &TestOpts,
725739
tests: ~[TestDescAndFn],
@@ -762,11 +776,11 @@ fn run_tests(opts: &TestOpts,
762776
pending += 1;
763777
}
764778

765-
let (desc, result) = p.recv();
779+
let (desc, result, stdout) = p.recv();
766780
if concurrency != 1 {
767781
callback(TeWait(desc.clone(), PadNone));
768782
}
769-
callback(TeResult(desc, result));
783+
callback(TeResult(desc, result, stdout));
770784
pending -= 1;
771785
}
772786

@@ -775,8 +789,8 @@ fn run_tests(opts: &TestOpts,
775789
for b in filtered_benchs_and_metrics.move_iter() {
776790
callback(TeWait(b.desc.clone(), b.testfn.padding()));
777791
run_test(!opts.run_benchmarks, b, ch.clone());
778-
let (test, result) = p.recv();
779-
callback(TeResult(test, result));
792+
let (test, result, stdout) = p.recv();
793+
callback(TeResult(test, result, stdout));
780794
}
781795
}
782796

@@ -865,7 +879,7 @@ pub fn run_test(force_ignore: bool,
865879
let TestDescAndFn {desc, testfn} = test;
866880

867881
if force_ignore || desc.ignore {
868-
monitor_ch.send((desc, TrIgnored));
882+
monitor_ch.send((desc, TrIgnored, ~[]));
869883
return;
870884
}
871885

@@ -875,40 +889,43 @@ pub fn run_test(force_ignore: bool,
875889
let testfn_cell = ::std::cell::Cell::new(testfn);
876890
do task::spawn {
877891
let mut task = task::task();
892+
let (mut reader, writer) = io::comm::stream();
878893
task.name(match desc.name {
879894
DynTestName(ref name) => SendStrOwned(name.clone()),
880895
StaticTestName(name) => SendStrStatic(name),
881896
});
897+
task.opts.stdout = Some(~writer as ~Writer);
882898
let result_future = task.future_result();
883899
task.spawn(testfn_cell.take());
884900

901+
let stdout = reader.read_to_end();
885902
let task_result = result_future.recv();
886903
let test_result = calc_result(&desc, task_result.is_ok());
887-
monitor_ch.send((desc.clone(), test_result));
904+
monitor_ch.send((desc.clone(), test_result, stdout));
888905
}
889906
}
890907

891908
match testfn {
892909
DynBenchFn(bencher) => {
893910
let bs = ::test::bench::benchmark(|harness| bencher.run(harness));
894-
monitor_ch.send((desc, TrBench(bs)));
911+
monitor_ch.send((desc, TrBench(bs), ~[]));
895912
return;
896913
}
897914
StaticBenchFn(benchfn) => {
898915
let bs = ::test::bench::benchmark(benchfn);
899-
monitor_ch.send((desc, TrBench(bs)));
916+
monitor_ch.send((desc, TrBench(bs), ~[]));
900917
return;
901918
}
902919
DynMetricFn(f) => {
903920
let mut mm = MetricMap::new();
904921
f(&mut mm);
905-
monitor_ch.send((desc, TrMetrics(mm)));
922+
monitor_ch.send((desc, TrMetrics(mm), ~[]));
906923
return;
907924
}
908925
StaticMetricFn(f) => {
909926
let mut mm = MetricMap::new();
910927
f(&mut mm);
911-
monitor_ch.send((desc, TrMetrics(mm)));
928+
monitor_ch.send((desc, TrMetrics(mm), ~[]));
912929
return;
913930
}
914931
DynTestFn(f) => run_test_inner(desc, monitor_ch, f),

src/libstd/io/comm.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Readers and Writers using channels as the underlying transport
12+
//!
13+
//! This module is useful for communication between tasks through Readers and
14+
//! Writers. This implements the Reader/Writer interface on top of the Port/Chan
15+
//! types to provide a blocking `read` method and an always-asynchronous `write`
16+
//! method.
17+
//!
18+
//! # Example
19+
//!
20+
//! use std::io::comm;
21+
//!
22+
//! let (mut reader, writer) = comm::stream();
23+
//!
24+
//! do spawn {
25+
//! let mut writer = writer;
26+
//! writer.write([1, 2, 3]);
27+
//! }
28+
//!
29+
//! assert_eq!(reader.read_to_end(), ~[1, 2, 3]);
30+
31+
use cmp;
32+
use comm;
33+
use comm::{GenericChan, GenericPort, Port, Chan};
34+
use io::{Writer, Reader};
35+
use option::{Some, None, Option};
36+
use vec;
37+
use vec::{ImmutableVector, CopyableVector};
38+
39+
/// Creates a new connected (reader, writer) pair. All data written on the
40+
/// writer will show up on the reader.
41+
pub fn stream() -> (PortReader, ChanWriter) {
42+
let (port, chan) = comm::stream();
43+
(PortReader { prev: None, port: port, pos: 0 }, ChanWriter { chan: chan })
44+
}
45+
46+
/// Wrapper struct around an internal communication port to read data from
47+
pub struct PortReader {
48+
priv prev: Option<~[u8]>,
49+
priv pos: uint,
50+
priv port: Port<~[u8]>,
51+
}
52+
53+
/// Wrapper struct around a channel to implement a `Writer` interface.
54+
pub struct ChanWriter {
55+
priv chan: Chan<~[u8]>,
56+
}
57+
58+
impl Writer for ChanWriter {
59+
fn write(&mut self, data: &[u8]) {
60+
self.chan.send(data.to_owned());
61+
}
62+
}
63+
64+
impl Reader for PortReader {
65+
fn read(&mut self, data: &mut [u8]) -> Option<uint> {
66+
let mut offset = 0;
67+
while offset < data.len() {
68+
match self.prev {
69+
Some(ref b) => {
70+
let dst = data.mut_slice_from(offset);
71+
let src = b.slice_from(self.pos);
72+
let amt = cmp::min(dst.len(), src.len());
73+
vec::bytes::copy_memory(dst, src, amt);
74+
self.pos += amt;
75+
offset += amt;
76+
if self.pos < b.len() {
77+
break
78+
}
79+
}
80+
None => {}
81+
}
82+
self.prev = self.port.try_recv();
83+
self.pos = 0;
84+
if self.prev.is_none() { break }
85+
}
86+
if offset == 0 {
87+
None
88+
} else {
89+
Some(offset)
90+
}
91+
}
92+
93+
fn eof(&mut self) -> bool { false }
94+
}
95+
96+
#[cfg(test)]
97+
mod tests {
98+
use prelude::*;
99+
100+
use io::comm::stream;
101+
use util;
102+
103+
#[test]
104+
fn smoke() {
105+
let (mut reader, mut writer) = stream();
106+
writer.write([1, 2, 3, 4]);
107+
util::ignore(writer);
108+
109+
assert_eq!(reader.read_byte(), Some(1));
110+
let mut buf = [0, 0];
111+
assert_eq!(reader.read(buf), Some(2));
112+
assert_eq!(buf[0], 2);
113+
assert_eq!(buf[1], 3);
114+
115+
assert_eq!(reader.read(buf), Some(1));
116+
assert_eq!(buf[0], 4);
117+
118+
assert_eq!(reader.read(buf), None);
119+
}
120+
121+
#[test]
122+
fn parallel() {
123+
let (mut reader, writer) = stream();
124+
do spawn {
125+
let mut writer = writer;
126+
writer.write([1, 2]);
127+
writer.write([3]);
128+
writer.write([4]);
129+
}
130+
131+
assert_eq!(reader.read_byte(), Some(1));
132+
let mut buf = [0, 0];
133+
assert_eq!(reader.read(buf), Some(2));
134+
assert_eq!(buf[0], 2);
135+
assert_eq!(buf[1], 3);
136+
137+
assert_eq!(reader.read(buf), Some(1));
138+
assert_eq!(buf[0], 4);
139+
140+
assert_eq!(reader.read(buf), None);
141+
}
142+
}

src/libstd/io/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ pub mod flate;
298298
/// Interop between byte streams and pipes. Not sure where it belongs
299299
pub mod comm_adapters;
300300

301+
pub mod comm;
302+
301303
/// Extension traits
302304
pub mod extensions;
303305

src/libstd/logging.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ start, print out all modules registered for logging, and then exit.
9292
use fmt;
9393
use option::*;
9494
use rt::local::Local;
95-
use rt::logging::{Logger, StdErrLogger};
9695
use rt::task::Task;
96+
use std::io;
97+
use std::io::buffered::LineBufferedWriter;
98+
use std::io::stdio;
9799

98100
/// This function is called directly by the compiler when using the logging
99101
/// macros. This function does not take into account whether the log level
@@ -110,18 +112,18 @@ pub fn log(_level: u32, args: &fmt::Arguments) {
110112
// Lazily initialize the local task's logger
111113
match (*local).logger {
112114
// Use the available logger if we have one
113-
Some(ref mut logger) => { logger.log(args); }
115+
Some(ref mut logger) => { fmt::writeln(*logger, args); }
114116
None => {
115-
let mut logger = StdErrLogger::new();
116-
logger.log(args);
117-
(*local).logger = Some(logger);
117+
let mut logger = LineBufferedWriter::new(stdio::stderr());
118+
fmt::writeln(&mut logger as &mut io::Writer, args);
119+
(*local).logger = Some(~logger as ~io::Writer);
118120
}
119121
}
120122
}
121123
// If there's no local task, then always log to stderr
122124
None => {
123-
let mut logger = StdErrLogger::new();
124-
logger.log(args);
125+
let mut logger = stdio::stderr();
126+
fmt::writeln(&mut logger as &mut io::Writer, args);
125127
}
126128
}
127129
}

0 commit comments

Comments
 (0)