]> git.lizzy.rs Git - rust.git/blob - src/libtest/lib.rs
4a693ac2218bf1604ca083ae30d03952f479dc0b
[rust.git] / src / libtest / lib.rs
1 // Copyright 2012-2014 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 //! Support code for rustc's built in unit-test and micro-benchmarking
12 //! framework.
13 //!
14 //! Almost all user code will only be interested in `Bencher` and
15 //! `black_box`. All other interactions (such as writing tests and
16 //! benchmarks themselves) should be done via the `#[test]` and
17 //! `#[bench]` attributes.
18 //!
19 //! See the [Testing Guide](../guide-testing.html) for more details.
20
21 // Currently, not much of this is meant for users. It is intended to
22 // support the simplest interface possible for representing and
23 // running tests while providing a base that other test frameworks may
24 // build off of.
25
26 #![crate_name = "test"]
27 #![unstable]
28 #![staged_api]
29 #![crate_type = "rlib"]
30 #![crate_type = "dylib"]
31 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
32        html_favicon_url = "http://www.rust-lang.org/favicon.ico",
33        html_root_url = "http://doc.rust-lang.org/nightly/")]
34 #![allow(unknown_features)]
35 #![feature(asm, slicing_syntax)]
36 #![feature(box_syntax)]
37 #![allow(unknown_features)] #![feature(int_uint)]
38 #![allow(unstable)]
39
40 extern crate getopts;
41 extern crate regex;
42 extern crate serialize;
43 extern crate "serialize" as rustc_serialize;
44 extern crate term;
45
46 pub use self::TestFn::*;
47 pub use self::MetricChange::*;
48 pub use self::ColorConfig::*;
49 pub use self::TestResult::*;
50 pub use self::TestName::*;
51 use self::TestEvent::*;
52 use self::NamePadding::*;
53 use self::OutputLocation::*;
54
55 use stats::Stats;
56 use getopts::{OptGroup, optflag, optopt};
57 use regex::Regex;
58 use serialize::{json, Decodable, Encodable};
59 use term::Terminal;
60 use term::color::{Color, RED, YELLOW, GREEN, CYAN};
61
62 use std::any::Any;
63 use std::cmp;
64 use std::collections::BTreeMap;
65 use std::f64;
66 use std::fmt::Show;
67 use std::fmt;
68 use std::io::fs::PathExtensions;
69 use std::io::stdio::StdWriter;
70 use std::io::{File, ChanReader, ChanWriter};
71 use std::io;
72 use std::iter::repeat;
73 use std::num::{Float, Int};
74 use std::os;
75 use std::str::FromStr;
76 use std::sync::mpsc::{channel, Sender};
77 use std::thread::{self, Thread};
78 use std::thunk::{Thunk, Invoke};
79 use std::time::Duration;
80
81 // to be used by rustc to compile tests in libtest
82 pub mod test {
83     pub use {Bencher, TestName, TestResult, TestDesc,
84              TestDescAndFn, TestOpts, TrFailed, TrIgnored, TrOk,
85              Metric, MetricMap, MetricAdded, MetricRemoved,
86              MetricChange, Improvement, Regression, LikelyNoise,
87              StaticTestFn, StaticTestName, DynTestName, DynTestFn,
88              run_test, test_main, test_main_static, filter_tests,
89              parse_opts, StaticBenchFn, ShouldFail};
90 }
91
92 pub mod stats;
93
94 // The name of a test. By convention this follows the rules for rust
95 // paths; i.e. it should be a series of identifiers separated by double
96 // colons. This way if some test runner wants to arrange the tests
97 // hierarchically it may.
98
99 #[derive(Clone, PartialEq, Eq, Hash, Show)]
100 pub enum TestName {
101     StaticTestName(&'static str),
102     DynTestName(String)
103 }
104 impl TestName {
105     fn as_slice<'a>(&'a self) -> &'a str {
106         match *self {
107             StaticTestName(s) => s,
108             DynTestName(ref s) => s.as_slice()
109         }
110     }
111 }
112 impl fmt::String for TestName {
113     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114         fmt::String::fmt(self.as_slice(), f)
115     }
116 }
117
118 #[derive(Clone, Copy)]
119 enum NamePadding {
120     PadNone,
121     PadOnLeft,
122     PadOnRight,
123 }
124
125 impl TestDesc {
126     fn padded_name(&self, column_count: uint, align: NamePadding) -> String {
127         let mut name = String::from_str(self.name.as_slice());
128         let fill = column_count.saturating_sub(name.len());
129         let mut pad = repeat(" ").take(fill).collect::<String>();
130         match align {
131             PadNone => name,
132             PadOnLeft => {
133                 pad.push_str(name.as_slice());
134                 pad
135             }
136             PadOnRight => {
137                 name.push_str(pad.as_slice());
138                 name
139             }
140         }
141     }
142 }
143
144 /// Represents a benchmark function.
145 pub trait TDynBenchFn {
146     fn run(&self, harness: &mut Bencher);
147 }
148
149 // A function that runs a test. If the function returns successfully,
150 // the test succeeds; if the function panics then the test fails. We
151 // may need to come up with a more clever definition of test in order
152 // to support isolation of tests into tasks.
153 pub enum TestFn {
154     StaticTestFn(fn()),
155     StaticBenchFn(fn(&mut Bencher)),
156     StaticMetricFn(fn(&mut MetricMap)),
157     DynTestFn(Thunk),
158     DynMetricFn(Box<for<'a> Invoke<&'a mut MetricMap>+'static>),
159     DynBenchFn(Box<TDynBenchFn+'static>)
160 }
161
162 impl TestFn {
163     fn padding(&self) -> NamePadding {
164         match self {
165             &StaticTestFn(..)   => PadNone,
166             &StaticBenchFn(..)  => PadOnRight,
167             &StaticMetricFn(..) => PadOnRight,
168             &DynTestFn(..)      => PadNone,
169             &DynMetricFn(..)    => PadOnRight,
170             &DynBenchFn(..)     => PadOnRight,
171         }
172     }
173 }
174
175 impl fmt::Show for TestFn {
176     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177         f.write_str(match *self {
178             StaticTestFn(..) => "StaticTestFn(..)",
179             StaticBenchFn(..) => "StaticBenchFn(..)",
180             StaticMetricFn(..) => "StaticMetricFn(..)",
181             DynTestFn(..) => "DynTestFn(..)",
182             DynMetricFn(..) => "DynMetricFn(..)",
183             DynBenchFn(..) => "DynBenchFn(..)"
184         })
185     }
186 }
187
188 /// Manager of the benchmarking runs.
189 ///
190 /// This is fed into functions marked with `#[bench]` to allow for
191 /// set-up & tear-down before running a piece of code repeatedly via a
192 /// call to `iter`.
193 #[derive(Copy)]
194 pub struct Bencher {
195     iterations: u64,
196     dur: Duration,
197     pub bytes: u64,
198 }
199
200 #[derive(Copy, Clone, Show, PartialEq, Eq, Hash)]
201 pub enum ShouldFail {
202     No,
203     Yes(Option<&'static str>)
204 }
205
206 // The definition of a single test. A test runner will run a list of
207 // these.
208 #[derive(Clone, Show, PartialEq, Eq, Hash)]
209 pub struct TestDesc {
210     pub name: TestName,
211     pub ignore: bool,
212     pub should_fail: ShouldFail,
213 }
214
215 unsafe impl Send for TestDesc {}
216
217 #[derive(Show)]
218 pub struct TestDescAndFn {
219     pub desc: TestDesc,
220     pub testfn: TestFn,
221 }
222
223 #[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Show, Copy)]
224 pub struct Metric {
225     value: f64,
226     noise: f64
227 }
228
229 impl Metric {
230     pub fn new(value: f64, noise: f64) -> Metric {
231         Metric {value: value, noise: noise}
232     }
233 }
234
235 #[derive(PartialEq)]
236 pub struct MetricMap(BTreeMap<String,Metric>);
237
238 impl Clone for MetricMap {
239     fn clone(&self) -> MetricMap {
240         let MetricMap(ref map) = *self;
241         MetricMap(map.clone())
242     }
243 }
244
245 /// Analysis of a single change in metric
246 #[derive(Copy, PartialEq, Show)]
247 pub enum MetricChange {
248     LikelyNoise,
249     MetricAdded,
250     MetricRemoved,
251     Improvement(f64),
252     Regression(f64)
253 }
254
255 pub type MetricDiff = BTreeMap<String,MetricChange>;
256
257 // The default console test runner. It accepts the command line
258 // arguments and a vector of test_descs.
259 pub fn test_main(args: &[String], tests: Vec<TestDescAndFn> ) {
260     let opts =
261         match parse_opts(args) {
262             Some(Ok(o)) => o,
263             Some(Err(msg)) => panic!("{:?}", msg),
264             None => return
265         };
266     match run_tests_console(&opts, tests) {
267         Ok(true) => {}
268         Ok(false) => panic!("Some tests failed"),
269         Err(e) => panic!("io error when running tests: {:?}", e),
270     }
271 }
272
273 // A variant optimized for invocation with a static test vector.
274 // This will panic (intentionally) when fed any dynamic tests, because
275 // it is copying the static values out into a dynamic vector and cannot
276 // copy dynamic values. It is doing this because from this point on
277 // a ~[TestDescAndFn] is used in order to effect ownership-transfer
278 // semantics into parallel test runners, which in turn requires a ~[]
279 // rather than a &[].
280 pub fn test_main_static(args: &[String], tests: &[TestDescAndFn]) {
281     let owned_tests = tests.iter().map(|t| {
282         match t.testfn {
283             StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: t.desc.clone() },
284             StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: t.desc.clone() },
285             _ => panic!("non-static tests passed to test::test_main_static")
286         }
287     }).collect();
288     test_main(args, owned_tests)
289 }
290
291 #[derive(Copy)]
292 pub enum ColorConfig {
293     AutoColor,
294     AlwaysColor,
295     NeverColor,
296 }
297
298 pub struct TestOpts {
299     pub filter: Option<Regex>,
300     pub run_ignored: bool,
301     pub run_tests: bool,
302     pub run_benchmarks: bool,
303     pub logfile: Option<Path>,
304     pub nocapture: bool,
305     pub color: ColorConfig,
306 }
307
308 impl TestOpts {
309     #[cfg(test)]
310     fn new() -> TestOpts {
311         TestOpts {
312             filter: None,
313             run_ignored: false,
314             run_tests: false,
315             run_benchmarks: false,
316             logfile: None,
317             nocapture: false,
318             color: AutoColor,
319         }
320     }
321 }
322
323 /// Result of parsing the options.
324 pub type OptRes = Result<TestOpts, String>;
325
326 fn optgroups() -> Vec<getopts::OptGroup> {
327     vec!(getopts::optflag("", "ignored", "Run ignored tests"),
328       getopts::optflag("", "test", "Run tests and not benchmarks"),
329       getopts::optflag("", "bench", "Run benchmarks instead of tests"),
330       getopts::optflag("h", "help", "Display this message (longer with --help)"),
331       getopts::optopt("", "logfile", "Write logs to the specified file instead \
332                           of stdout", "PATH"),
333       getopts::optflag("", "nocapture", "don't capture stdout/stderr of each \
334                                          task, allow printing directly"),
335       getopts::optopt("", "color", "Configure coloring of output:
336             auto   = colorize if stdout is a tty and tests are run on serially (default);
337             always = always colorize output;
338             never  = never colorize output;", "auto|always|never"))
339 }
340
341 fn usage(binary: &str) {
342     let message = format!("Usage: {} [OPTIONS] [FILTER]", binary);
343     println!(r#"{usage}
344
345 The FILTER regex is tested against the name of all tests to run, and
346 only those tests that match are run.
347
348 By default, all tests are run in parallel. This can be altered with the
349 RUST_TEST_TASKS environment variable when running tests (set it to 1).
350
351 All tests have their standard output and standard error captured by default.
352 This can be overridden with the --nocapture flag or the RUST_TEST_NOCAPTURE=1
353 environment variable. Logging is not captured by default.
354
355 Test Attributes:
356
357     #[test]        - Indicates a function is a test to be run. This function
358                      takes no arguments.
359     #[bench]       - Indicates a function is a benchmark to be run. This
360                      function takes one argument (test::Bencher).
361     #[should_fail] - This function (also labeled with #[test]) will only pass if
362                      the code causes a failure (an assertion failure or panic!)
363                      A message may be provided, which the failure string must
364                      contain: #[should_fail(expected = "foo")].
365     #[ignore]      - When applied to a function which is already attributed as a
366                      test, then the test runner will ignore these tests during
367                      normal test runs. Running with --ignored will run these
368                      tests."#,
369              usage = getopts::usage(message.as_slice(),
370                                     optgroups().as_slice()));
371 }
372
373 // Parses command line arguments into test options
374 pub fn parse_opts(args: &[String]) -> Option<OptRes> {
375     let args_ = args.tail();
376     let matches =
377         match getopts::getopts(args_.as_slice(), optgroups().as_slice()) {
378           Ok(m) => m,
379           Err(f) => return Some(Err(f.to_string()))
380         };
381
382     if matches.opt_present("h") { usage(args[0].as_slice()); return None; }
383
384     let filter = if matches.free.len() > 0 {
385         let s = matches.free[0].as_slice();
386         match Regex::new(s) {
387             Ok(re) => Some(re),
388             Err(e) => return Some(Err(format!("could not parse /{}/: {:?}", s, e)))
389         }
390     } else {
391         None
392     };
393
394     let run_ignored = matches.opt_present("ignored");
395
396     let logfile = matches.opt_str("logfile");
397     let logfile = logfile.map(|s| Path::new(s));
398
399     let run_benchmarks = matches.opt_present("bench");
400     let run_tests = ! run_benchmarks ||
401         matches.opt_present("test");
402
403     let mut nocapture = matches.opt_present("nocapture");
404     if !nocapture {
405         nocapture = os::getenv("RUST_TEST_NOCAPTURE").is_some();
406     }
407
408     let color = match matches.opt_str("color").as_ref().map(|s| s.as_slice()) {
409         Some("auto") | None => AutoColor,
410         Some("always") => AlwaysColor,
411         Some("never") => NeverColor,
412
413         Some(v) => return Some(Err(format!("argument for --color must be \
414                                             auto, always, or never (was {})",
415                                             v))),
416     };
417
418     let test_opts = TestOpts {
419         filter: filter,
420         run_ignored: run_ignored,
421         run_tests: run_tests,
422         run_benchmarks: run_benchmarks,
423         logfile: logfile,
424         nocapture: nocapture,
425         color: color,
426     };
427
428     Some(Ok(test_opts))
429 }
430
431 #[derive(Clone, PartialEq)]
432 pub struct BenchSamples {
433     ns_iter_summ: stats::Summary<f64>,
434     mb_s: uint,
435 }
436
437 #[derive(Clone, PartialEq)]
438 pub enum TestResult {
439     TrOk,
440     TrFailed,
441     TrIgnored,
442     TrMetrics(MetricMap),
443     TrBench(BenchSamples),
444 }
445
446 unsafe impl Send for TestResult {}
447
448 enum OutputLocation<T> {
449     Pretty(Box<term::Terminal<term::WriterWrapper> + Send>),
450     Raw(T),
451 }
452
453 struct ConsoleTestState<T> {
454     log_out: Option<File>,
455     out: OutputLocation<T>,
456     use_color: bool,
457     show_boxplot: bool,
458     boxplot_width: uint,
459     show_all_stats: bool,
460     total: uint,
461     passed: uint,
462     failed: uint,
463     ignored: uint,
464     measured: uint,
465     metrics: MetricMap,
466     failures: Vec<(TestDesc, Vec<u8> )> ,
467     max_name_len: uint, // number of columns to fill when aligning names
468 }
469
470 impl<T: Writer> ConsoleTestState<T> {
471     pub fn new(opts: &TestOpts,
472                _: Option<T>) -> io::IoResult<ConsoleTestState<StdWriter>> {
473         let log_out = match opts.logfile {
474             Some(ref path) => Some(try!(File::create(path))),
475             None => None
476         };
477         let out = match term::stdout() {
478             None => Raw(io::stdio::stdout_raw()),
479             Some(t) => Pretty(t)
480         };
481
482         Ok(ConsoleTestState {
483             out: out,
484             log_out: log_out,
485             use_color: use_color(opts),
486             show_boxplot: false,
487             boxplot_width: 50,
488             show_all_stats: false,
489             total: 0u,
490             passed: 0u,
491             failed: 0u,
492             ignored: 0u,
493             measured: 0u,
494             metrics: MetricMap::new(),
495             failures: Vec::new(),
496             max_name_len: 0u,
497         })
498     }
499
500     pub fn write_ok(&mut self) -> io::IoResult<()> {
501         self.write_pretty("ok", term::color::GREEN)
502     }
503
504     pub fn write_failed(&mut self) -> io::IoResult<()> {
505         self.write_pretty("FAILED", term::color::RED)
506     }
507
508     pub fn write_ignored(&mut self) -> io::IoResult<()> {
509         self.write_pretty("ignored", term::color::YELLOW)
510     }
511
512     pub fn write_metric(&mut self) -> io::IoResult<()> {
513         self.write_pretty("metric", term::color::CYAN)
514     }
515
516     pub fn write_bench(&mut self) -> io::IoResult<()> {
517         self.write_pretty("bench", term::color::CYAN)
518     }
519
520     pub fn write_added(&mut self) -> io::IoResult<()> {
521         self.write_pretty("added", term::color::GREEN)
522     }
523
524     pub fn write_improved(&mut self) -> io::IoResult<()> {
525         self.write_pretty("improved", term::color::GREEN)
526     }
527
528     pub fn write_removed(&mut self) -> io::IoResult<()> {
529         self.write_pretty("removed", term::color::YELLOW)
530     }
531
532     pub fn write_regressed(&mut self) -> io::IoResult<()> {
533         self.write_pretty("regressed", term::color::RED)
534     }
535
536     pub fn write_pretty(&mut self,
537                         word: &str,
538                         color: term::color::Color) -> io::IoResult<()> {
539         match self.out {
540             Pretty(ref mut term) => {
541                 if self.use_color {
542                     try!(term.fg(color));
543                 }
544                 try!(term.write(word.as_bytes()));
545                 if self.use_color {
546                     try!(term.reset());
547                 }
548                 Ok(())
549             }
550             Raw(ref mut stdout) => stdout.write(word.as_bytes())
551         }
552     }
553
554     pub fn write_plain(&mut self, s: &str) -> io::IoResult<()> {
555         match self.out {
556             Pretty(ref mut term) => term.write(s.as_bytes()),
557             Raw(ref mut stdout) => stdout.write(s.as_bytes())
558         }
559     }
560
561     pub fn write_run_start(&mut self, len: uint) -> io::IoResult<()> {
562         self.total = len;
563         let noun = if len != 1 { "tests" } else { "test" };
564         self.write_plain(format!("\nrunning {} {}\n", len, noun).as_slice())
565     }
566
567     pub fn write_test_start(&mut self, test: &TestDesc,
568                             align: NamePadding) -> io::IoResult<()> {
569         let name = test.padded_name(self.max_name_len, align);
570         self.write_plain(format!("test {} ... ", name).as_slice())
571     }
572
573     pub fn write_result(&mut self, result: &TestResult) -> io::IoResult<()> {
574         try!(match *result {
575             TrOk => self.write_ok(),
576             TrFailed => self.write_failed(),
577             TrIgnored => self.write_ignored(),
578             TrMetrics(ref mm) => {
579                 try!(self.write_metric());
580                 self.write_plain(format!(": {}", fmt_metrics(mm)).as_slice())
581             }
582             TrBench(ref bs) => {
583                 try!(self.write_bench());
584
585                 if self.show_boxplot {
586                     let mut wr = Vec::new();
587
588                     try!(stats::write_boxplot(&mut wr, &bs.ns_iter_summ, self.boxplot_width));
589
590                     let s = String::from_utf8(wr).unwrap();
591
592                     try!(self.write_plain(format!(": {}", s).as_slice()));
593                 }
594
595                 if self.show_all_stats {
596                     let mut wr = Vec::new();
597
598                     try!(stats::write_5_number_summary(&mut wr, &bs.ns_iter_summ));
599
600                     let s = String::from_utf8(wr).unwrap();
601
602                     try!(self.write_plain(format!(": {}", s).as_slice()));
603                 } else {
604                     try!(self.write_plain(format!(": {}",
605                                                   fmt_bench_samples(bs)).as_slice()));
606                 }
607
608                 Ok(())
609             }
610         });
611         self.write_plain("\n")
612     }
613
614     pub fn write_log(&mut self, test: &TestDesc,
615                      result: &TestResult) -> io::IoResult<()> {
616         match self.log_out {
617             None => Ok(()),
618             Some(ref mut o) => {
619                 let s = format!("{} {}\n", match *result {
620                         TrOk => "ok".to_string(),
621                         TrFailed => "failed".to_string(),
622                         TrIgnored => "ignored".to_string(),
623                         TrMetrics(ref mm) => fmt_metrics(mm),
624                         TrBench(ref bs) => fmt_bench_samples(bs)
625                     }, test.name.as_slice());
626                 o.write(s.as_bytes())
627             }
628         }
629     }
630
631     pub fn write_failures(&mut self) -> io::IoResult<()> {
632         try!(self.write_plain("\nfailures:\n"));
633         let mut failures = Vec::new();
634         let mut fail_out = String::new();
635         for &(ref f, ref stdout) in self.failures.iter() {
636             failures.push(f.name.to_string());
637             if stdout.len() > 0 {
638                 fail_out.push_str(format!("---- {} stdout ----\n\t",
639                                           f.name.as_slice()).as_slice());
640                 let output = String::from_utf8_lossy(stdout.as_slice());
641                 fail_out.push_str(output.as_slice());
642                 fail_out.push_str("\n");
643             }
644         }
645         if fail_out.len() > 0 {
646             try!(self.write_plain("\n"));
647             try!(self.write_plain(fail_out.as_slice()));
648         }
649
650         try!(self.write_plain("\nfailures:\n"));
651         failures.sort();
652         for name in failures.iter() {
653             try!(self.write_plain(format!("    {}\n",
654                                           name.as_slice()).as_slice()));
655         }
656         Ok(())
657     }
658
659     pub fn write_metric_diff(&mut self, diff: &MetricDiff) -> io::IoResult<()> {
660         let mut noise = 0u;
661         let mut improved = 0u;
662         let mut regressed = 0u;
663         let mut added = 0u;
664         let mut removed = 0u;
665
666         for (k, v) in diff.iter() {
667             match *v {
668                 LikelyNoise => noise += 1,
669                 MetricAdded => {
670                     added += 1;
671                     try!(self.write_added());
672                     try!(self.write_plain(format!(": {}\n", *k).as_slice()));
673                 }
674                 MetricRemoved => {
675                     removed += 1;
676                     try!(self.write_removed());
677                     try!(self.write_plain(format!(": {}\n", *k).as_slice()));
678                 }
679                 Improvement(pct) => {
680                     improved += 1;
681                     try!(self.write_plain(format!(": {} ", *k).as_slice()));
682                     try!(self.write_improved());
683                     try!(self.write_plain(format!(" by {:.2}%\n",
684                                                   pct as f64).as_slice()));
685                 }
686                 Regression(pct) => {
687                     regressed += 1;
688                     try!(self.write_plain(format!(": {} ", *k).as_slice()));
689                     try!(self.write_regressed());
690                     try!(self.write_plain(format!(" by {:.2}%\n",
691                                                   pct as f64).as_slice()));
692                 }
693             }
694         }
695         try!(self.write_plain(format!("result of ratchet: {} metrics added, \
696                                         {} removed, {} improved, {} regressed, \
697                                         {} noise\n",
698                                        added, removed, improved, regressed,
699                                        noise).as_slice()));
700         if regressed == 0 {
701             try!(self.write_plain("updated ratchet file\n"));
702         } else {
703             try!(self.write_plain("left ratchet file untouched\n"));
704         }
705         Ok(())
706     }
707
708     pub fn write_run_finish(&mut self,
709                             ratchet_metrics: &Option<Path>,
710                             ratchet_pct: Option<f64>) -> io::IoResult<bool> {
711         assert!(self.passed + self.failed + self.ignored + self.measured == self.total);
712
713         let ratchet_success = match *ratchet_metrics {
714             None => true,
715             Some(ref pth) => {
716                 try!(self.write_plain(format!("\nusing metrics ratchet: {:?}\n",
717                                               pth.display()).as_slice()));
718                 match ratchet_pct {
719                     None => (),
720                     Some(pct) =>
721                         try!(self.write_plain(format!("with noise-tolerance \
722                                                          forced to: {}%\n",
723                                                         pct).as_slice()))
724                 }
725                 let (diff, ok) = self.metrics.ratchet(pth, ratchet_pct);
726                 try!(self.write_metric_diff(&diff));
727                 ok
728             }
729         };
730
731         let test_success = self.failed == 0u;
732         if !test_success {
733             try!(self.write_failures());
734         }
735
736         let success = ratchet_success && test_success;
737
738         try!(self.write_plain("\ntest result: "));
739         if success {
740             // There's no parallelism at this point so it's safe to use color
741             try!(self.write_ok());
742         } else {
743             try!(self.write_failed());
744         }
745         let s = format!(". {} passed; {} failed; {} ignored; {} measured\n\n",
746                         self.passed, self.failed, self.ignored, self.measured);
747         try!(self.write_plain(s.as_slice()));
748         return Ok(success);
749     }
750 }
751
752 pub fn fmt_metrics(mm: &MetricMap) -> String {
753     let MetricMap(ref mm) = *mm;
754     let v : Vec<String> = mm.iter()
755         .map(|(k,v)| format!("{}: {} (+/- {})", *k,
756                              v.value as f64, v.noise as f64))
757         .collect();
758     v.connect(", ")
759 }
760
761 pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
762     if bs.mb_s != 0 {
763         format!("{:>9} ns/iter (+/- {}) = {} MB/s",
764              bs.ns_iter_summ.median as uint,
765              (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint,
766              bs.mb_s)
767     } else {
768         format!("{:>9} ns/iter (+/- {})",
769              bs.ns_iter_summ.median as uint,
770              (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint)
771     }
772 }
773
774 // A simple console test runner
775 pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn> ) -> io::IoResult<bool> {
776
777     fn callback<T: Writer>(event: &TestEvent, st: &mut ConsoleTestState<T>) -> io::IoResult<()> {
778         match (*event).clone() {
779             TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
780             TeWait(ref test, padding) => st.write_test_start(test, padding),
781             TeResult(test, result, stdout) => {
782                 try!(st.write_log(&test, &result));
783                 try!(st.write_result(&result));
784                 match result {
785                     TrOk => st.passed += 1,
786                     TrIgnored => st.ignored += 1,
787                     TrMetrics(mm) => {
788                         let tname = test.name.as_slice();
789                         let MetricMap(mm) = mm;
790                         for (k,v) in mm.iter() {
791                             st.metrics
792                               .insert_metric(format!("{}.{}",
793                                                      tname,
794                                                      k).as_slice(),
795                                              v.value,
796                                              v.noise);
797                         }
798                         st.measured += 1
799                     }
800                     TrBench(bs) => {
801                         st.metrics.insert_metric(test.name.as_slice(),
802                                                  bs.ns_iter_summ.median,
803                                                  bs.ns_iter_summ.max - bs.ns_iter_summ.min);
804                         st.measured += 1
805                     }
806                     TrFailed => {
807                         st.failed += 1;
808                         st.failures.push((test, stdout));
809                     }
810                 }
811                 Ok(())
812             }
813         }
814     }
815
816     let mut st = try!(ConsoleTestState::new(opts, None::<StdWriter>));
817     fn len_if_padded(t: &TestDescAndFn) -> uint {
818         match t.testfn.padding() {
819             PadNone => 0u,
820             PadOnLeft | PadOnRight => t.desc.name.as_slice().len(),
821         }
822     }
823     match tests.iter().max_by(|t|len_if_padded(*t)) {
824         Some(t) => {
825             let n = t.desc.name.as_slice();
826             st.max_name_len = n.len();
827         },
828         None => {}
829     }
830     try!(run_tests(opts, tests, |x| callback(&x, &mut st)));
831     return st.write_run_finish(&None, None);
832 }
833
834 #[test]
835 fn should_sort_failures_before_printing_them() {
836     let test_a = TestDesc {
837         name: StaticTestName("a"),
838         ignore: false,
839         should_fail: ShouldFail::No
840     };
841
842     let test_b = TestDesc {
843         name: StaticTestName("b"),
844         ignore: false,
845         should_fail: ShouldFail::No
846     };
847
848     let mut st = ConsoleTestState {
849         log_out: None,
850         out: Raw(Vec::new()),
851         use_color: false,
852         show_boxplot: false,
853         boxplot_width: 0,
854         show_all_stats: false,
855         total: 0u,
856         passed: 0u,
857         failed: 0u,
858         ignored: 0u,
859         measured: 0u,
860         max_name_len: 10u,
861         metrics: MetricMap::new(),
862         failures: vec!((test_b, Vec::new()), (test_a, Vec::new()))
863     };
864
865     st.write_failures().unwrap();
866     let s = match st.out {
867         Raw(ref m) => String::from_utf8_lossy(&m[]),
868         Pretty(_) => unreachable!()
869     };
870
871     let apos = s.find_str("a").unwrap();
872     let bpos = s.find_str("b").unwrap();
873     assert!(apos < bpos);
874 }
875
876 fn use_color(opts: &TestOpts) -> bool {
877     match opts.color {
878         AutoColor => get_concurrency() == 1 && io::stdout().get_ref().isatty(),
879         AlwaysColor => true,
880         NeverColor => false,
881     }
882 }
883
884 #[derive(Clone)]
885 enum TestEvent {
886     TeFiltered(Vec<TestDesc> ),
887     TeWait(TestDesc, NamePadding),
888     TeResult(TestDesc, TestResult, Vec<u8> ),
889 }
890
891 pub type MonitorMsg = (TestDesc, TestResult, Vec<u8> );
892
893
894 fn run_tests<F>(opts: &TestOpts,
895                 tests: Vec<TestDescAndFn> ,
896                 mut callback: F) -> io::IoResult<()> where
897     F: FnMut(TestEvent) -> io::IoResult<()>,
898 {
899     let filtered_tests = filter_tests(opts, tests);
900     let filtered_descs = filtered_tests.iter()
901                                        .map(|t| t.desc.clone())
902                                        .collect();
903
904     try!(callback(TeFiltered(filtered_descs)));
905
906     let (filtered_tests, filtered_benchs_and_metrics): (Vec<_>, _) =
907         filtered_tests.into_iter().partition(|e| {
908             match e.testfn {
909                 StaticTestFn(_) | DynTestFn(_) => true,
910                 _ => false
911             }
912         });
913
914     // It's tempting to just spawn all the tests at once, but since we have
915     // many tests that run in other processes we would be making a big mess.
916     let concurrency = get_concurrency();
917
918     let mut remaining = filtered_tests;
919     remaining.reverse();
920     let mut pending = 0;
921
922     let (tx, rx) = channel::<MonitorMsg>();
923
924     while pending > 0 || !remaining.is_empty() {
925         while pending < concurrency && !remaining.is_empty() {
926             let test = remaining.pop().unwrap();
927             if concurrency == 1 {
928                 // We are doing one test at a time so we can print the name
929                 // of the test before we run it. Useful for debugging tests
930                 // that hang forever.
931                 try!(callback(TeWait(test.desc.clone(), test.testfn.padding())));
932             }
933             run_test(opts, !opts.run_tests, test, tx.clone());
934             pending += 1;
935         }
936
937         let (desc, result, stdout) = rx.recv().unwrap();
938         if concurrency != 1 {
939             try!(callback(TeWait(desc.clone(), PadNone)));
940         }
941         try!(callback(TeResult(desc, result, stdout)));
942         pending -= 1;
943     }
944
945     // All benchmarks run at the end, in serial.
946     // (this includes metric fns)
947     for b in filtered_benchs_and_metrics.into_iter() {
948         try!(callback(TeWait(b.desc.clone(), b.testfn.padding())));
949         run_test(opts, !opts.run_benchmarks, b, tx.clone());
950         let (test, result, stdout) = rx.recv().unwrap();
951         try!(callback(TeResult(test, result, stdout)));
952     }
953     Ok(())
954 }
955
956 fn get_concurrency() -> uint {
957     use std::rt;
958     match os::getenv("RUST_TEST_TASKS") {
959         Some(s) => {
960             let opt_n: Option<uint> = FromStr::from_str(s.as_slice());
961             match opt_n {
962                 Some(n) if n > 0 => n,
963                 _ => panic!("RUST_TEST_TASKS is `{}`, should be a positive integer.", s)
964             }
965         }
966         None => {
967             rt::default_sched_threads()
968         }
969     }
970 }
971
972 pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescAndFn> {
973     let mut filtered = tests;
974
975     // Remove tests that don't match the test filter
976     filtered = match opts.filter {
977         None => filtered,
978         Some(ref re) => {
979             filtered.into_iter()
980                 .filter(|test| re.is_match(test.desc.name.as_slice())).collect()
981         }
982     };
983
984     // Maybe pull out the ignored test and unignore them
985     filtered = if !opts.run_ignored {
986         filtered
987     } else {
988         fn filter(test: TestDescAndFn) -> Option<TestDescAndFn> {
989             if test.desc.ignore {
990                 let TestDescAndFn {desc, testfn} = test;
991                 Some(TestDescAndFn {
992                     desc: TestDesc {ignore: false, ..desc},
993                     testfn: testfn
994                 })
995             } else {
996                 None
997             }
998         };
999         filtered.into_iter().filter_map(|x| filter(x)).collect()
1000     };
1001
1002     // Sort the tests alphabetically
1003     filtered.sort_by(|t1, t2| t1.desc.name.as_slice().cmp(t2.desc.name.as_slice()));
1004
1005     filtered
1006 }
1007
1008 pub fn run_test(opts: &TestOpts,
1009                 force_ignore: bool,
1010                 test: TestDescAndFn,
1011                 monitor_ch: Sender<MonitorMsg>) {
1012
1013     let TestDescAndFn {desc, testfn} = test;
1014
1015     if force_ignore || desc.ignore {
1016         monitor_ch.send((desc, TrIgnored, Vec::new())).unwrap();
1017         return;
1018     }
1019
1020     fn run_test_inner(desc: TestDesc,
1021                       monitor_ch: Sender<MonitorMsg>,
1022                       nocapture: bool,
1023                       testfn: Thunk) {
1024         Thread::spawn(move || {
1025             let (tx, rx) = channel();
1026             let mut reader = ChanReader::new(rx);
1027             let stdout = ChanWriter::new(tx.clone());
1028             let stderr = ChanWriter::new(tx);
1029             let mut cfg = thread::Builder::new().name(match desc.name {
1030                 DynTestName(ref name) => name.clone().to_string(),
1031                 StaticTestName(name) => name.to_string(),
1032             });
1033             if nocapture {
1034                 drop((stdout, stderr));
1035             } else {
1036                 cfg = cfg.stdout(box stdout as Box<Writer + Send>);
1037                 cfg = cfg.stderr(box stderr as Box<Writer + Send>);
1038             }
1039
1040             let result_guard = cfg.scoped(move || { testfn.invoke(()) });
1041             let stdout = reader.read_to_end().unwrap().into_iter().collect();
1042             let test_result = calc_result(&desc, result_guard.join());
1043             monitor_ch.send((desc.clone(), test_result, stdout)).unwrap();
1044         });
1045     }
1046
1047     match testfn {
1048         DynBenchFn(bencher) => {
1049             let bs = ::bench::benchmark(|harness| bencher.run(harness));
1050             monitor_ch.send((desc, TrBench(bs), Vec::new())).unwrap();
1051             return;
1052         }
1053         StaticBenchFn(benchfn) => {
1054             let bs = ::bench::benchmark(|harness| (benchfn.clone())(harness));
1055             monitor_ch.send((desc, TrBench(bs), Vec::new())).unwrap();
1056             return;
1057         }
1058         DynMetricFn(f) => {
1059             let mut mm = MetricMap::new();
1060             f.invoke(&mut mm);
1061             monitor_ch.send((desc, TrMetrics(mm), Vec::new())).unwrap();
1062             return;
1063         }
1064         StaticMetricFn(f) => {
1065             let mut mm = MetricMap::new();
1066             f(&mut mm);
1067             monitor_ch.send((desc, TrMetrics(mm), Vec::new())).unwrap();
1068             return;
1069         }
1070         DynTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture, f),
1071         StaticTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture,
1072                                           Thunk::new(move|| f()))
1073     }
1074 }
1075
1076 fn calc_result(desc: &TestDesc, task_result: Result<(), Box<Any+Send>>) -> TestResult {
1077     match (&desc.should_fail, task_result) {
1078         (&ShouldFail::No, Ok(())) |
1079         (&ShouldFail::Yes(None), Err(_)) => TrOk,
1080         (&ShouldFail::Yes(Some(msg)), Err(ref err))
1081             if err.downcast_ref::<String>()
1082                 .map(|e| &**e)
1083                 .or_else(|| err.downcast_ref::<&'static str>().map(|e| *e))
1084                 .map(|e| e.contains(msg))
1085                 .unwrap_or(false) => TrOk,
1086         _ => TrFailed,
1087     }
1088 }
1089
1090 impl MetricMap {
1091
1092     pub fn new() -> MetricMap {
1093         MetricMap(BTreeMap::new())
1094     }
1095
1096     /// Load MetricDiff from a file.
1097     ///
1098     /// # Panics
1099     ///
1100     /// This function will panic if the path does not exist or the path does not
1101     /// contain a valid metric map.
1102     pub fn load(p: &Path) -> MetricMap {
1103         assert!(p.exists());
1104         let mut f = File::open(p).unwrap();
1105         let value = json::from_reader(&mut f as &mut io::Reader).unwrap();
1106         let mut decoder = json::Decoder::new(value);
1107         MetricMap(match Decodable::decode(&mut decoder) {
1108             Ok(t) => t,
1109             Err(e) => panic!("failure decoding JSON: {:?}", e)
1110         })
1111     }
1112
1113     /// Write MetricDiff to a file.
1114     pub fn save(&self, p: &Path) -> io::IoResult<()> {
1115         let mut file = try!(File::create(p));
1116         let MetricMap(ref map) = *self;
1117         write!(&mut file, "{}", json::as_json(map))
1118     }
1119
1120     /// Compare against another MetricMap. Optionally compare all
1121     /// measurements in the maps using the provided `noise_pct` as a
1122     /// percentage of each value to consider noise. If `None`, each
1123     /// measurement's noise threshold is independently chosen as the
1124     /// maximum of that measurement's recorded noise quantity in either
1125     /// map.
1126     pub fn compare_to_old(&self, old: &MetricMap,
1127                           noise_pct: Option<f64>) -> MetricDiff {
1128         let mut diff : MetricDiff = BTreeMap::new();
1129         let MetricMap(ref selfmap) = *self;
1130         let MetricMap(ref old) = *old;
1131         for (k, vold) in old.iter() {
1132             let r = match selfmap.get(k) {
1133                 None => MetricRemoved,
1134                 Some(v) => {
1135                     let delta = v.value - vold.value;
1136                     let noise = match noise_pct {
1137                         None => vold.noise.abs().max(v.noise.abs()),
1138                         Some(pct) => vold.value * pct / 100.0
1139                     };
1140                     if delta.abs() <= noise {
1141                         LikelyNoise
1142                     } else {
1143                         let pct = delta.abs() / vold.value.max(f64::EPSILON) * 100.0;
1144                         if vold.noise < 0.0 {
1145                             // When 'noise' is negative, it means we want
1146                             // to see deltas that go up over time, and can
1147                             // only tolerate slight negative movement.
1148                             if delta < 0.0 {
1149                                 Regression(pct)
1150                             } else {
1151                                 Improvement(pct)
1152                             }
1153                         } else {
1154                             // When 'noise' is positive, it means we want
1155                             // to see deltas that go down over time, and
1156                             // can only tolerate slight positive movements.
1157                             if delta < 0.0 {
1158                                 Improvement(pct)
1159                             } else {
1160                                 Regression(pct)
1161                             }
1162                         }
1163                     }
1164                 }
1165             };
1166             diff.insert((*k).clone(), r);
1167         }
1168         let MetricMap(ref map) = *self;
1169         for (k, _) in map.iter() {
1170             if !diff.contains_key(k) {
1171                 diff.insert((*k).clone(), MetricAdded);
1172             }
1173         }
1174         diff
1175     }
1176
1177     /// Insert a named `value` (+/- `noise`) metric into the map. The value
1178     /// must be non-negative. The `noise` indicates the uncertainty of the
1179     /// metric, which doubles as the "noise range" of acceptable
1180     /// pairwise-regressions on this named value, when comparing from one
1181     /// metric to the next using `compare_to_old`.
1182     ///
1183     /// If `noise` is positive, then it means this metric is of a value
1184     /// you want to see grow smaller, so a change larger than `noise` in the
1185     /// positive direction represents a regression.
1186     ///
1187     /// If `noise` is negative, then it means this metric is of a value
1188     /// you want to see grow larger, so a change larger than `noise` in the
1189     /// negative direction represents a regression.
1190     pub fn insert_metric(&mut self, name: &str, value: f64, noise: f64) {
1191         let m = Metric {
1192             value: value,
1193             noise: noise
1194         };
1195         let MetricMap(ref mut map) = *self;
1196         map.insert(name.to_string(), m);
1197     }
1198
1199     /// Attempt to "ratchet" an external metric file. This involves loading
1200     /// metrics from a metric file (if it exists), comparing against
1201     /// the metrics in `self` using `compare_to_old`, and rewriting the
1202     /// file to contain the metrics in `self` if none of the
1203     /// `MetricChange`s are `Regression`. Returns the diff as well
1204     /// as a boolean indicating whether the ratchet succeeded.
1205     pub fn ratchet(&self, p: &Path, pct: Option<f64>) -> (MetricDiff, bool) {
1206         let old = if p.exists() {
1207             MetricMap::load(p)
1208         } else {
1209             MetricMap::new()
1210         };
1211
1212         let diff : MetricDiff = self.compare_to_old(&old, pct);
1213         let ok = diff.iter().all(|(_, v)| {
1214             match *v {
1215                 Regression(_) => false,
1216                 _ => true
1217             }
1218         });
1219
1220         if ok {
1221             self.save(p).unwrap();
1222         }
1223         return (diff, ok)
1224     }
1225 }
1226
1227
1228 // Benchmarking
1229
1230 /// A function that is opaque to the optimizer, to allow benchmarks to
1231 /// pretend to use outputs to assist in avoiding dead-code
1232 /// elimination.
1233 ///
1234 /// This function is a no-op, and does not even read from `dummy`.
1235 pub fn black_box<T>(dummy: T) -> T {
1236     // we need to "use" the argument in some way LLVM can't
1237     // introspect.
1238     unsafe {asm!("" : : "r"(&dummy))}
1239     dummy
1240 }
1241
1242
1243 impl Bencher {
1244     /// Callback for benchmark functions to run in their body.
1245     pub fn iter<T, F>(&mut self, mut inner: F) where F: FnMut() -> T {
1246         self.dur = Duration::span(|| {
1247             let k = self.iterations;
1248             for _ in range(0u64, k) {
1249                 black_box(inner());
1250             }
1251         });
1252     }
1253
1254     pub fn ns_elapsed(&mut self) -> u64 {
1255         self.dur.num_nanoseconds().unwrap() as u64
1256     }
1257
1258     pub fn ns_per_iter(&mut self) -> u64 {
1259         if self.iterations == 0 {
1260             0
1261         } else {
1262             self.ns_elapsed() / cmp::max(self.iterations, 1)
1263         }
1264     }
1265
1266     pub fn bench_n<F>(&mut self, n: u64, f: F) where F: FnOnce(&mut Bencher) {
1267         self.iterations = n;
1268         f(self);
1269     }
1270
1271     // This is a more statistics-driven benchmark algorithm
1272     pub fn auto_bench<F>(&mut self, mut f: F) -> stats::Summary<f64> where F: FnMut(&mut Bencher) {
1273         // Initial bench run to get ballpark figure.
1274         let mut n = 1_u64;
1275         self.bench_n(n, |x| f(x));
1276
1277         // Try to estimate iter count for 1ms falling back to 1m
1278         // iterations if first run took < 1ns.
1279         if self.ns_per_iter() == 0 {
1280             n = 1_000_000;
1281         } else {
1282             n = 1_000_000 / cmp::max(self.ns_per_iter(), 1);
1283         }
1284         // if the first run took more than 1ms we don't want to just
1285         // be left doing 0 iterations on every loop. The unfortunate
1286         // side effect of not being able to do as many runs is
1287         // automatically handled by the statistical analysis below
1288         // (i.e. larger error bars).
1289         if n == 0 { n = 1; }
1290
1291         let mut total_run = Duration::nanoseconds(0);
1292         let samples : &mut [f64] = &mut [0.0_f64; 50];
1293         loop {
1294             let mut summ = None;
1295             let mut summ5 = None;
1296
1297             let loop_run = Duration::span(|| {
1298
1299                 for p in samples.iter_mut() {
1300                     self.bench_n(n, |x| f(x));
1301                     *p = self.ns_per_iter() as f64;
1302                 };
1303
1304                 stats::winsorize(samples, 5.0);
1305                 summ = Some(stats::Summary::new(samples));
1306
1307                 for p in samples.iter_mut() {
1308                     self.bench_n(5 * n, |x| f(x));
1309                     *p = self.ns_per_iter() as f64;
1310                 };
1311
1312                 stats::winsorize(samples, 5.0);
1313                 summ5 = Some(stats::Summary::new(samples));
1314             });
1315             let summ = summ.unwrap();
1316             let summ5 = summ5.unwrap();
1317
1318             // If we've run for 100ms and seem to have converged to a
1319             // stable median.
1320             if loop_run.num_milliseconds() > 100 &&
1321                 summ.median_abs_dev_pct < 1.0 &&
1322                 summ.median - summ5.median < summ5.median_abs_dev {
1323                 return summ5;
1324             }
1325
1326             total_run = total_run + loop_run;
1327             // Longest we ever run for is 3s.
1328             if total_run.num_seconds() > 3 {
1329                 return summ5;
1330             }
1331
1332             n *= 2;
1333         }
1334     }
1335 }
1336
1337 pub mod bench {
1338     use std::cmp;
1339     use std::time::Duration;
1340     use super::{Bencher, BenchSamples};
1341
1342     pub fn benchmark<F>(f: F) -> BenchSamples where F: FnMut(&mut Bencher) {
1343         let mut bs = Bencher {
1344             iterations: 0,
1345             dur: Duration::nanoseconds(0),
1346             bytes: 0
1347         };
1348
1349         let ns_iter_summ = bs.auto_bench(f);
1350
1351         let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
1352         let iter_s = 1_000_000_000 / ns_iter;
1353         let mb_s = (bs.bytes * iter_s) / 1_000_000;
1354
1355         BenchSamples {
1356             ns_iter_summ: ns_iter_summ,
1357             mb_s: mb_s as uint
1358         }
1359     }
1360 }
1361
1362 #[cfg(test)]
1363 mod tests {
1364     use test::{TrFailed, TrIgnored, TrOk, filter_tests, parse_opts,
1365                TestDesc, TestDescAndFn, TestOpts, run_test,
1366                Metric, MetricMap, MetricAdded, MetricRemoved,
1367                Improvement, Regression, LikelyNoise,
1368                StaticTestName, DynTestName, DynTestFn, ShouldFail};
1369     use std::io::TempDir;
1370     use std::thunk::Thunk;
1371     use std::sync::mpsc::channel;
1372
1373     #[test]
1374     pub fn do_not_run_ignored_tests() {
1375         fn f() { panic!(); }
1376         let desc = TestDescAndFn {
1377             desc: TestDesc {
1378                 name: StaticTestName("whatever"),
1379                 ignore: true,
1380                 should_fail: ShouldFail::No,
1381             },
1382             testfn: DynTestFn(Thunk::new(move|| f())),
1383         };
1384         let (tx, rx) = channel();
1385         run_test(&TestOpts::new(), false, desc, tx);
1386         let (_, res, _) = rx.recv().unwrap();
1387         assert!(res != TrOk);
1388     }
1389
1390     #[test]
1391     pub fn ignored_tests_result_in_ignored() {
1392         fn f() { }
1393         let desc = TestDescAndFn {
1394             desc: TestDesc {
1395                 name: StaticTestName("whatever"),
1396                 ignore: true,
1397                 should_fail: ShouldFail::No,
1398             },
1399             testfn: DynTestFn(Thunk::new(move|| f())),
1400         };
1401         let (tx, rx) = channel();
1402         run_test(&TestOpts::new(), false, desc, tx);
1403         let (_, res, _) = rx.recv().unwrap();
1404         assert!(res == TrIgnored);
1405     }
1406
1407     #[test]
1408     fn test_should_fail() {
1409         fn f() { panic!(); }
1410         let desc = TestDescAndFn {
1411             desc: TestDesc {
1412                 name: StaticTestName("whatever"),
1413                 ignore: false,
1414                 should_fail: ShouldFail::Yes(None)
1415             },
1416             testfn: DynTestFn(Thunk::new(move|| f())),
1417         };
1418         let (tx, rx) = channel();
1419         run_test(&TestOpts::new(), false, desc, tx);
1420         let (_, res, _) = rx.recv().unwrap();
1421         assert!(res == TrOk);
1422     }
1423
1424     #[test]
1425     fn test_should_fail_good_message() {
1426         fn f() { panic!("an error message"); }
1427         let desc = TestDescAndFn {
1428             desc: TestDesc {
1429                 name: StaticTestName("whatever"),
1430                 ignore: false,
1431                 should_fail: ShouldFail::Yes(Some("error message"))
1432             },
1433             testfn: DynTestFn(Thunk::new(move|| f())),
1434         };
1435         let (tx, rx) = channel();
1436         run_test(&TestOpts::new(), false, desc, tx);
1437         let (_, res, _) = rx.recv().unwrap();
1438         assert!(res == TrOk);
1439     }
1440
1441     #[test]
1442     fn test_should_fail_bad_message() {
1443         fn f() { panic!("an error message"); }
1444         let desc = TestDescAndFn {
1445             desc: TestDesc {
1446                 name: StaticTestName("whatever"),
1447                 ignore: false,
1448                 should_fail: ShouldFail::Yes(Some("foobar"))
1449             },
1450             testfn: DynTestFn(Thunk::new(move|| f())),
1451         };
1452         let (tx, rx) = channel();
1453         run_test(&TestOpts::new(), false, desc, tx);
1454         let (_, res, _) = rx.recv().unwrap();
1455         assert!(res == TrFailed);
1456     }
1457
1458     #[test]
1459     fn test_should_fail_but_succeeds() {
1460         fn f() { }
1461         let desc = TestDescAndFn {
1462             desc: TestDesc {
1463                 name: StaticTestName("whatever"),
1464                 ignore: false,
1465                 should_fail: ShouldFail::Yes(None)
1466             },
1467             testfn: DynTestFn(Thunk::new(move|| f())),
1468         };
1469         let (tx, rx) = channel();
1470         run_test(&TestOpts::new(), false, desc, tx);
1471         let (_, res, _) = rx.recv().unwrap();
1472         assert!(res == TrFailed);
1473     }
1474
1475     #[test]
1476     fn first_free_arg_should_be_a_filter() {
1477         let args = vec!("progname".to_string(), "some_regex_filter".to_string());
1478         let opts = match parse_opts(args.as_slice()) {
1479             Some(Ok(o)) => o,
1480             _ => panic!("Malformed arg in first_free_arg_should_be_a_filter")
1481         };
1482         assert!(opts.filter.expect("should've found filter").is_match("some_regex_filter"))
1483     }
1484
1485     #[test]
1486     fn parse_ignored_flag() {
1487         let args = vec!("progname".to_string(),
1488                         "filter".to_string(),
1489                         "--ignored".to_string());
1490         let opts = match parse_opts(args.as_slice()) {
1491             Some(Ok(o)) => o,
1492             _ => panic!("Malformed arg in parse_ignored_flag")
1493         };
1494         assert!((opts.run_ignored));
1495     }
1496
1497     #[test]
1498     pub fn filter_for_ignored_option() {
1499         // When we run ignored tests the test filter should filter out all the
1500         // unignored tests and flip the ignore flag on the rest to false
1501
1502         let mut opts = TestOpts::new();
1503         opts.run_tests = true;
1504         opts.run_ignored = true;
1505
1506         let tests = vec!(
1507             TestDescAndFn {
1508                 desc: TestDesc {
1509                     name: StaticTestName("1"),
1510                     ignore: true,
1511                     should_fail: ShouldFail::No,
1512                 },
1513                 testfn: DynTestFn(Thunk::new(move|| {})),
1514             },
1515             TestDescAndFn {
1516                 desc: TestDesc {
1517                     name: StaticTestName("2"),
1518                     ignore: false,
1519                     should_fail: ShouldFail::No,
1520                 },
1521                 testfn: DynTestFn(Thunk::new(move|| {})),
1522             });
1523         let filtered = filter_tests(&opts, tests);
1524
1525         assert_eq!(filtered.len(), 1);
1526         assert_eq!(filtered[0].desc.name.to_string(),
1527                    "1");
1528         assert!(filtered[0].desc.ignore == false);
1529     }
1530
1531     #[test]
1532     pub fn sort_tests() {
1533         let mut opts = TestOpts::new();
1534         opts.run_tests = true;
1535
1536         let names =
1537             vec!("sha1::test".to_string(),
1538                  "int::test_to_str".to_string(),
1539                  "int::test_pow".to_string(),
1540                  "test::do_not_run_ignored_tests".to_string(),
1541                  "test::ignored_tests_result_in_ignored".to_string(),
1542                  "test::first_free_arg_should_be_a_filter".to_string(),
1543                  "test::parse_ignored_flag".to_string(),
1544                  "test::filter_for_ignored_option".to_string(),
1545                  "test::sort_tests".to_string());
1546         let tests =
1547         {
1548             fn testfn() { }
1549             let mut tests = Vec::new();
1550             for name in names.iter() {
1551                 let test = TestDescAndFn {
1552                     desc: TestDesc {
1553                         name: DynTestName((*name).clone()),
1554                         ignore: false,
1555                         should_fail: ShouldFail::No,
1556                     },
1557                     testfn: DynTestFn(Thunk::new(testfn)),
1558                 };
1559                 tests.push(test);
1560             }
1561             tests
1562         };
1563         let filtered = filter_tests(&opts, tests);
1564
1565         let expected =
1566             vec!("int::test_pow".to_string(),
1567                  "int::test_to_str".to_string(),
1568                  "sha1::test".to_string(),
1569                  "test::do_not_run_ignored_tests".to_string(),
1570                  "test::filter_for_ignored_option".to_string(),
1571                  "test::first_free_arg_should_be_a_filter".to_string(),
1572                  "test::ignored_tests_result_in_ignored".to_string(),
1573                  "test::parse_ignored_flag".to_string(),
1574                  "test::sort_tests".to_string());
1575
1576         for (a, b) in expected.iter().zip(filtered.iter()) {
1577             assert!(*a == b.desc.name.to_string());
1578         }
1579     }
1580
1581     #[test]
1582     pub fn filter_tests_regex() {
1583         let mut opts = TestOpts::new();
1584         opts.filter = Some(::regex::Regex::new("a.*b.+c").unwrap());
1585
1586         let mut names = ["yes::abXc", "yes::aXXXbXXXXc",
1587                          "no::XYZ", "no::abc"];
1588         names.sort();
1589
1590         fn test_fn() {}
1591         let tests = names.iter().map(|name| {
1592             TestDescAndFn {
1593                 desc: TestDesc {
1594                     name: DynTestName(name.to_string()),
1595                     ignore: false,
1596                     should_fail: ShouldFail::No,
1597                 },
1598                 testfn: DynTestFn(Thunk::new(test_fn))
1599             }
1600         }).collect();
1601         let filtered = filter_tests(&opts, tests);
1602
1603         let expected: Vec<&str> =
1604             names.iter().map(|&s| s).filter(|name| name.starts_with("yes")).collect();
1605
1606         assert_eq!(filtered.len(), expected.len());
1607         for (test, expected_name) in filtered.iter().zip(expected.iter()) {
1608             assert_eq!(test.desc.name.as_slice(), *expected_name);
1609         }
1610     }
1611
1612     #[test]
1613     pub fn test_metricmap_compare() {
1614         let mut m1 = MetricMap::new();
1615         let mut m2 = MetricMap::new();
1616         m1.insert_metric("in-both-noise", 1000.0, 200.0);
1617         m2.insert_metric("in-both-noise", 1100.0, 200.0);
1618
1619         m1.insert_metric("in-first-noise", 1000.0, 2.0);
1620         m2.insert_metric("in-second-noise", 1000.0, 2.0);
1621
1622         m1.insert_metric("in-both-want-downwards-but-regressed", 1000.0, 10.0);
1623         m2.insert_metric("in-both-want-downwards-but-regressed", 2000.0, 10.0);
1624
1625         m1.insert_metric("in-both-want-downwards-and-improved", 2000.0, 10.0);
1626         m2.insert_metric("in-both-want-downwards-and-improved", 1000.0, 10.0);
1627
1628         m1.insert_metric("in-both-want-upwards-but-regressed", 2000.0, -10.0);
1629         m2.insert_metric("in-both-want-upwards-but-regressed", 1000.0, -10.0);
1630
1631         m1.insert_metric("in-both-want-upwards-and-improved", 1000.0, -10.0);
1632         m2.insert_metric("in-both-want-upwards-and-improved", 2000.0, -10.0);
1633
1634         let diff1 = m2.compare_to_old(&m1, None);
1635
1636         assert_eq!(*(diff1.get(&"in-both-noise".to_string()).unwrap()), LikelyNoise);
1637         assert_eq!(*(diff1.get(&"in-first-noise".to_string()).unwrap()), MetricRemoved);
1638         assert_eq!(*(diff1.get(&"in-second-noise".to_string()).unwrap()), MetricAdded);
1639         assert_eq!(*(diff1.get(&"in-both-want-downwards-but-regressed".to_string()).unwrap()),
1640                    Regression(100.0));
1641         assert_eq!(*(diff1.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1642                    Improvement(50.0));
1643         assert_eq!(*(diff1.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1644                    Regression(50.0));
1645         assert_eq!(*(diff1.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1646                    Improvement(100.0));
1647         assert_eq!(diff1.len(), 7);
1648
1649         let diff2 = m2.compare_to_old(&m1, Some(200.0));
1650
1651         assert_eq!(*(diff2.get(&"in-both-noise".to_string()).unwrap()), LikelyNoise);
1652         assert_eq!(*(diff2.get(&"in-first-noise".to_string()).unwrap()), MetricRemoved);
1653         assert_eq!(*(diff2.get(&"in-second-noise".to_string()).unwrap()), MetricAdded);
1654         assert_eq!(*(diff2.get(&"in-both-want-downwards-but-regressed".to_string()).unwrap()),
1655                    LikelyNoise);
1656         assert_eq!(*(diff2.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1657                    LikelyNoise);
1658         assert_eq!(*(diff2.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1659                    LikelyNoise);
1660         assert_eq!(*(diff2.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1661                    LikelyNoise);
1662         assert_eq!(diff2.len(), 7);
1663     }
1664
1665     #[test]
1666     pub fn ratchet_test() {
1667
1668         let dpth = TempDir::new("test-ratchet").ok().expect("missing test for ratchet");
1669         let pth = dpth.path().join("ratchet.json");
1670
1671         let mut m1 = MetricMap::new();
1672         m1.insert_metric("runtime", 1000.0, 2.0);
1673         m1.insert_metric("throughput", 50.0, 2.0);
1674
1675         let mut m2 = MetricMap::new();
1676         m2.insert_metric("runtime", 1100.0, 2.0);
1677         m2.insert_metric("throughput", 50.0, 2.0);
1678
1679         m1.save(&pth).unwrap();
1680
1681         // Ask for a ratchet that should fail to advance.
1682         let (diff1, ok1) = m2.ratchet(&pth, None);
1683         assert_eq!(ok1, false);
1684         assert_eq!(diff1.len(), 2);
1685         assert_eq!(*(diff1.get(&"runtime".to_string()).unwrap()), Regression(10.0));
1686         assert_eq!(*(diff1.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1687
1688         // Check that it was not rewritten.
1689         let m3 = MetricMap::load(&pth);
1690         let MetricMap(m3) = m3;
1691         assert_eq!(m3.len(), 2);
1692         assert_eq!(*(m3.get(&"runtime".to_string()).unwrap()), Metric::new(1000.0, 2.0));
1693         assert_eq!(*(m3.get(&"throughput".to_string()).unwrap()), Metric::new(50.0, 2.0));
1694
1695         // Ask for a ratchet with an explicit noise-percentage override,
1696         // that should advance.
1697         let (diff2, ok2) = m2.ratchet(&pth, Some(10.0));
1698         assert_eq!(ok2, true);
1699         assert_eq!(diff2.len(), 2);
1700         assert_eq!(*(diff2.get(&"runtime".to_string()).unwrap()), LikelyNoise);
1701         assert_eq!(*(diff2.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1702
1703         // Check that it was rewritten.
1704         let m4 = MetricMap::load(&pth);
1705         let MetricMap(m4) = m4;
1706         assert_eq!(m4.len(), 2);
1707         assert_eq!(*(m4.get(&"runtime".to_string()).unwrap()), Metric::new(1100.0, 2.0));
1708         assert_eq!(*(m4.get(&"throughput".to_string()).unwrap()), Metric::new(50.0, 2.0));
1709     }
1710 }