lib.rs 61.4 KB
Newer Older
L
Liigo Zhuang 已提交
1
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 3 4 5 6 7 8 9 10
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

11 12 13
//! Support code for rustc's built in unit-test and micro-benchmarking
//! framework.
//!
14
//! Almost all user code will only be interested in `Bencher` and
15 16 17 18 19 20 21 22 23 24
//! `black_box`. All other interactions (such as writing tests and
//! benchmarks themselves) should be done via the `#[test]` and
//! `#[bench]` attributes.
//!
//! See the [Testing Guide](../guide-testing.html) for more details.

// Currently, not much of this is meant for users. It is intended to
// support the simplest interface possible for representing and
// running tests while providing a base that other test frameworks may
// build off of.
25

A
Alex Crichton 已提交
26
#![crate_name = "test"]
27
#![experimental]
28 29 30 31
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
32
       html_root_url = "http://doc.rust-lang.org/nightly/")]
L
Liigo Zhuang 已提交
33

34
#![allow(unknown_features)]
K
Keegan McAllister 已提交
35
#![feature(asm, globs, slicing_syntax)]
36
#![feature(unboxed_closures, default_type_params)]
37
#![feature(old_orphan_check)]
L
Liigo Zhuang 已提交
38

A
Alex Crichton 已提交
39
extern crate getopts;
40
extern crate regex;
L
Liigo Zhuang 已提交
41
extern crate serialize;
42
extern crate "serialize" as rustc_serialize;
A
Alex Crichton 已提交
43
extern crate term;
44

S
Steven Fackler 已提交
45 46 47 48 49 50 51 52 53
pub use self::TestFn::*;
pub use self::MetricChange::*;
pub use self::ColorConfig::*;
pub use self::TestResult::*;
pub use self::TestName::*;
use self::TestEvent::*;
use self::NamePadding::*;
use self::OutputLocation::*;

54
use stats::Stats;
L
Liigo Zhuang 已提交
55
use getopts::{OptGroup, optflag, optopt};
56
use regex::Regex;
57
use serialize::{json, Decodable, Encodable};
L
Liigo Zhuang 已提交
58 59
use term::Terminal;
use term::color::{Color, RED, YELLOW, GREEN, CYAN};
60

61
use std::any::Any;
M
Michael Darakananda 已提交
62
use std::cmp;
63
use std::collections::BTreeMap;
64
use std::f64;
65
use std::fmt::Show;
66
use std::fmt;
67
use std::io::fs::PathExtensions;
L
Luqman Aden 已提交
68
use std::io::stdio::StdWriter;
69
use std::io::{File, ChanReader, ChanWriter};
70
use std::io;
A
Alex Crichton 已提交
71
use std::iter::repeat;
72
use std::num::{Float, Int};
73
use std::os;
A
Alex Crichton 已提交
74
use std::str::FromStr;
75
use std::sync::mpsc::{channel, Sender};
76
use std::thread::{self, Thread};
77
use std::thunk::{Thunk, Invoke};
78
use std::time::Duration;
79

L
Liigo Zhuang 已提交
80 81
// to be used by rustc to compile tests in libtest
pub mod test {
82
    pub use {Bencher, TestName, TestResult, TestDesc,
L
Liigo Zhuang 已提交
83 84 85 86 87
             TestDescAndFn, TestOpts, TrFailed, TrIgnored, TrOk,
             Metric, MetricMap, MetricAdded, MetricRemoved,
             MetricChange, Improvement, Regression, LikelyNoise,
             StaticTestFn, StaticTestName, DynTestName, DynTestFn,
             run_test, test_main, test_main_static, filter_tests,
88
             parse_opts, StaticBenchFn, ShouldFail};
L
Liigo Zhuang 已提交
89 90
}

91 92
pub mod stats;

93
// The name of a test. By convention this follows the rules for rust
S
Sean Moon 已提交
94
// paths; i.e. it should be a series of identifiers separated by double
95
// colons. This way if some test runner wants to arrange the tests
96
// hierarchically it may.
97

98
#[derive(Clone, PartialEq, Eq, Hash)]
99
pub enum TestName {
100
    StaticTestName(&'static str),
101
    DynTestName(String)
102
}
103 104
impl TestName {
    fn as_slice<'a>(&'a self) -> &'a str {
105
        match *self {
106 107
            StaticTestName(s) => s,
            DynTestName(ref s) => s.as_slice()
108 109 110
        }
    }
}
111 112 113 114 115
impl Show for TestName {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_slice().fmt(f)
    }
}
116

117
#[derive(Clone, Copy)]
N
Niko Matsakis 已提交
118 119 120 121 122 123
enum NamePadding {
    PadNone,
    PadOnLeft,
    PadOnRight,
}

124
impl TestDesc {
125 126
    fn padded_name(&self, column_count: uint, align: NamePadding) -> String {
        let mut name = String::from_str(self.name.as_slice());
127
        let fill = column_count.saturating_sub(name.len());
A
Alex Crichton 已提交
128
        let mut pad = repeat(" ").take(fill).collect::<String>();
129
        match align {
130
            PadNone => name,
131 132
            PadOnLeft => {
                pad.push_str(name.as_slice());
133
                pad
134 135 136
            }
            PadOnRight => {
                name.push_str(pad.as_slice());
137
                name
138
            }
139 140 141 142
        }
    }
}

143 144
/// Represents a benchmark function.
pub trait TDynBenchFn {
145
    fn run(&self, harness: &mut Bencher);
146 147
}

148
// A function that runs a test. If the function returns successfully,
S
Steve Klabnik 已提交
149
// the test succeeds; if the function panics then the test fails. We
150 151
// may need to come up with a more clever definition of test in order
// to support isolation of tests into tasks.
152
pub enum TestFn {
153
    StaticTestFn(fn()),
154
    StaticBenchFn(fn(&mut Bencher)),
155 156 157
    StaticMetricFn(fn(&mut MetricMap)),
    DynTestFn(Thunk),
    DynMetricFn(Box<for<'a> Invoke<&'a mut MetricMap>+'static>),
158
    DynBenchFn(Box<TDynBenchFn+'static>)
159 160
}

161 162 163
impl TestFn {
    fn padding(&self) -> NamePadding {
        match self {
A
Alex Crichton 已提交
164 165 166 167 168 169
            &StaticTestFn(..)   => PadNone,
            &StaticBenchFn(..)  => PadOnRight,
            &StaticMetricFn(..) => PadOnRight,
            &DynTestFn(..)      => PadNone,
            &DynMetricFn(..)    => PadOnRight,
            &DynBenchFn(..)     => PadOnRight,
170 171 172 173
        }
    }
}

174 175
impl fmt::Show for TestFn {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176
        f.write_str(match *self {
177 178 179 180 181 182
            StaticTestFn(..) => "StaticTestFn(..)",
            StaticBenchFn(..) => "StaticBenchFn(..)",
            StaticMetricFn(..) => "StaticMetricFn(..)",
            DynTestFn(..) => "DynTestFn(..)",
            DynMetricFn(..) => "DynMetricFn(..)",
            DynBenchFn(..) => "DynBenchFn(..)"
183
        })
184 185 186
    }
}

187 188 189 190 191
/// Manager of the benchmarking runs.
///
/// This is feed into functions marked with `#[bench]` to allow for
/// set-up & tear-down before running a piece of code repeatedly via a
/// call to `iter`.
192
#[derive(Copy)]
193
pub struct Bencher {
194
    iterations: u64,
195
    dur: Duration,
196
    pub bytes: u64,
197
}
198

199
#[derive(Copy, Clone, Show, PartialEq, Eq, Hash)]
200 201 202 203 204
pub enum ShouldFail {
    No,
    Yes(Option<&'static str>)
}

205 206
// The definition of a single test. A test runner will run a list of
// these.
207
#[derive(Clone, Show, PartialEq, Eq, Hash)]
208
pub struct TestDesc {
209 210
    pub name: TestName,
    pub ignore: bool,
211
    pub should_fail: ShouldFail,
212
}
213

214
#[derive(Show)]
215
pub struct TestDescAndFn {
216 217
    pub desc: TestDesc,
    pub testfn: TestFn,
218 219
}

220
#[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Show, Copy)]
221
pub struct Metric {
222 223
    value: f64,
    noise: f64
224 225
}

L
Liigo Zhuang 已提交
226 227 228 229 230 231
impl Metric {
    pub fn new(value: f64, noise: f64) -> Metric {
        Metric {value: value, noise: noise}
    }
}

232
#[derive(PartialEq)]
A
Alexis Beingessner 已提交
233
pub struct MetricMap(BTreeMap<String,Metric>);
234

235
impl Clone for MetricMap {
236
    fn clone(&self) -> MetricMap {
237 238
        let MetricMap(ref map) = *self;
        MetricMap(map.clone())
239 240 241
    }
}

242
/// Analysis of a single change in metric
243
#[derive(Copy, PartialEq, Show)]
244 245 246 247 248 249 250 251
pub enum MetricChange {
    LikelyNoise,
    MetricAdded,
    MetricRemoved,
    Improvement(f64),
    Regression(f64)
}

A
Alexis Beingessner 已提交
252
pub type MetricDiff = BTreeMap<String,MetricChange>;
253

254
// The default console test runner. It accepts the command line
255
// arguments and a vector of test_descs.
256
pub fn test_main(args: &[String], tests: Vec<TestDescAndFn> ) {
M
Marijn Haverbeke 已提交
257
    let opts =
258
        match parse_opts(args) {
B
Brian Anderson 已提交
259
            Some(Ok(o)) => o,
S
Steve Klabnik 已提交
260
            Some(Err(msg)) => panic!("{}", msg),
B
Brian Anderson 已提交
261
            None => return
M
Marijn Haverbeke 已提交
262
        };
A
Alex Crichton 已提交
263 264
    match run_tests_console(&opts, tests) {
        Ok(true) => {}
S
Steve Klabnik 已提交
265 266
        Ok(false) => panic!("Some tests failed"),
        Err(e) => panic!("io error when running tests: {}", e),
A
Alex Crichton 已提交
267
    }
268 269
}

270
// A variant optimized for invocation with a static test vector.
S
Steve Klabnik 已提交
271
// This will panic (intentionally) when fed any dynamic tests, because
272 273 274 275 276
// it is copying the static values out into a dynamic vector and cannot
// copy dynamic values. It is doing this because from this point on
// a ~[TestDescAndFn] is used in order to effect ownership-transfer
// semantics into parallel test runners, which in turn requires a ~[]
// rather than a &[].
277
pub fn test_main_static(args: &[String], tests: &[TestDescAndFn]) {
278
    let owned_tests = tests.iter().map(|t| {
279
        match t.testfn {
280 281
            StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: t.desc.clone() },
            StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: t.desc.clone() },
S
Steve Klabnik 已提交
282
            _ => panic!("non-static tests passed to test::test_main_static")
283
        }
284
    }).collect();
285 286 287
    test_main(args, owned_tests)
}

288
#[derive(Copy)]
289 290 291 292 293 294
pub enum ColorConfig {
    AutoColor,
    AlwaysColor,
    NeverColor,
}

295
pub struct TestOpts {
296
    pub filter: Option<Regex>,
297 298 299 300 301 302 303
    pub run_ignored: bool,
    pub run_tests: bool,
    pub run_benchmarks: bool,
    pub ratchet_metrics: Option<Path>,
    pub ratchet_noise_percent: Option<f64>,
    pub save_metrics: Option<Path>,
    pub test_shard: Option<(uint,uint)>,
304 305
    pub logfile: Option<Path>,
    pub nocapture: bool,
306
    pub color: ColorConfig,
307 308 309
    pub show_boxplot: bool,
    pub boxplot_width: uint,
    pub show_all_stats: bool,
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
}

impl TestOpts {
    #[cfg(test)]
    fn new() -> TestOpts {
        TestOpts {
            filter: None,
            run_ignored: false,
            run_tests: false,
            run_benchmarks: false,
            ratchet_metrics: None,
            ratchet_noise_percent: None,
            save_metrics: None,
            test_shard: None,
            logfile: None,
            nocapture: false,
326
            color: AutoColor,
327 328 329
            show_boxplot: false,
            boxplot_width: 50,
            show_all_stats: false,
330 331
        }
    }
332
}
333

334
/// Result of parsing the options.
335
pub type OptRes = Result<TestOpts, String>;
336

337 338
fn optgroups() -> Vec<getopts::OptGroup> {
    vec!(getopts::optflag("", "ignored", "Run ignored tests"),
339 340 341 342
      getopts::optflag("", "test", "Run tests and not benchmarks"),
      getopts::optflag("", "bench", "Run benchmarks instead of tests"),
      getopts::optflag("h", "help", "Display this message (longer with --help)"),
      getopts::optopt("", "save-metrics", "Location to save bench metrics",
343
                     "PATH"),
344
      getopts::optopt("", "ratchet-metrics",
345 346 347
                     "Location to load and save metrics from. The metrics \
                      loaded are cause benchmarks to fail if they run too \
                      slowly", "PATH"),
348
      getopts::optopt("", "ratchet-noise-percent",
349 350
                     "Tests within N% of the recorded metrics will be \
                      considered as passing", "PERCENTAGE"),
351
      getopts::optopt("", "logfile", "Write logs to the specified file instead \
352
                          of stdout", "PATH"),
353
      getopts::optopt("", "test-shard", "run shard A, of B shards, worth of the testsuite",
354 355
                     "A.B"),
      getopts::optflag("", "nocapture", "don't capture stdout/stderr of each \
356 357 358 359
                                         task, allow printing directly"),
      getopts::optopt("", "color", "Configure coloring of output:
            auto   = colorize if stdout is a tty and tests are run on serially (default);
            always = always colorize output;
360 361 362 363
            never  = never colorize output;", "auto|always|never"),
      getopts::optflag("", "boxplot", "Display a boxplot of the benchmark statistics"),
      getopts::optopt("", "boxplot-width", "Set the boxplot width (default 50)", "WIDTH"),
      getopts::optflag("", "stats", "Display the benchmark min, max, and quartiles"))
364 365
}

366
fn usage(binary: &str) {
A
Alex Crichton 已提交
367
    let message = format!("Usage: {} [OPTIONS] [FILTER]", binary);
368
    println!(r#"{usage}
369 370

The FILTER regex is tested against the name of all tests to run, and
371
only those tests that match are run.
372 373

By default, all tests are run in parallel. This can be altered with the
374
RUST_TEST_TASKS environment variable when running tests (set it to 1).
375

376 377 378 379
All tests have their standard output and standard error captured by default.
This can be overridden with the --nocapture flag or the RUST_TEST_NOCAPTURE=1
environment variable. Logging is not captured by default.

380 381
Test Attributes:

A
Alex Crichton 已提交
382
    #[test]        - Indicates a function is a test to be run. This function
383
                     takes no arguments.
A
Alex Crichton 已提交
384
    #[bench]       - Indicates a function is a benchmark to be run. This
385
                     function takes one argument (test::Bencher).
A
Alex Crichton 已提交
386
    #[should_fail] - This function (also labeled with #[test]) will only pass if
S
Steve Klabnik 已提交
387
                     the code causes a failure (an assertion failure or panic!)
388
                     A message may be provided, which the failure string must
S
Steven Fackler 已提交
389
                     contain: #[should_fail(expected = "foo")].
A
Alex Crichton 已提交
390
    #[ignore]      - When applied to a function which is already attributed as a
391 392
                     test, then the test runner will ignore these tests during
                     normal test runs. Running with --ignored will run these
393
                     tests."#,
394 395
             usage = getopts::usage(message.as_slice(),
                                    optgroups().as_slice()));
396 397
}

398
// Parses command line arguments into test options
399
pub fn parse_opts(args: &[String]) -> Option<OptRes> {
400
    let args_ = args.tail();
401
    let matches =
402
        match getopts::getopts(args_.as_slice(), optgroups().as_slice()) {
L
Luqman Aden 已提交
403
          Ok(m) => m,
404
          Err(f) => return Some(Err(f.to_string()))
M
Marijn Haverbeke 已提交
405 406
        };

407
    if matches.opt_present("h") { usage(args[0].as_slice()); return None; }
408

409
    let filter = if matches.free.len() > 0 {
N
Nick Cameron 已提交
410
        let s = matches.free[0].as_slice();
411 412
        match Regex::new(s) {
            Ok(re) => Some(re),
A
Alex Crichton 已提交
413
            Err(e) => return Some(Err(format!("could not parse /{}/: {}", s, e)))
414 415 416 417
        }
    } else {
        None
    };
418

419
    let run_ignored = matches.opt_present("ignored");
420

421
    let logfile = matches.opt_str("logfile");
422
    let logfile = logfile.map(|s| Path::new(s));
423

424
    let run_benchmarks = matches.opt_present("bench");
425
    let run_tests = ! run_benchmarks ||
426
        matches.opt_present("test");
427

428
    let ratchet_metrics = matches.opt_str("ratchet-metrics");
429
    let ratchet_metrics = ratchet_metrics.map(|s| Path::new(s));
430

431
    let ratchet_noise_percent = matches.opt_str("ratchet-noise-percent");
432
    let ratchet_noise_percent =
A
Alex Crichton 已提交
433
        ratchet_noise_percent.map(|s| s.as_slice().parse::<f64>().unwrap());
434

435
    let save_metrics = matches.opt_str("save-metrics");
436
    let save_metrics = save_metrics.map(|s| Path::new(s));
437

438
    let test_shard = matches.opt_str("test-shard");
439
    let test_shard = opt_shard(test_shard);
440

441 442 443 444 445
    let mut nocapture = matches.opt_present("nocapture");
    if !nocapture {
        nocapture = os::getenv("RUST_TEST_NOCAPTURE").is_some();
    }

446 447 448 449 450 451 452 453 454 455
    let color = match matches.opt_str("color").as_ref().map(|s| s.as_slice()) {
        Some("auto") | None => AutoColor,
        Some("always") => AlwaysColor,
        Some("never") => NeverColor,

        Some(v) => return Some(Err(format!("argument for --color must be \
                                            auto, always, or never (was {})",
                                            v))),
    };

456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
    let show_boxplot = matches.opt_present("boxplot");
    let boxplot_width = match matches.opt_str("boxplot-width") {
        Some(width) => {
            match FromStr::from_str(width.as_slice()) {
                Some(width) => width,
                None => {
                    return Some(Err(format!("argument for --boxplot-width must be a uint")));
                }
            }
        }
        None => 50,
    };

    let show_all_stats = matches.opt_present("stats");

471 472 473
    let test_opts = TestOpts {
        filter: filter,
        run_ignored: run_ignored,
474 475
        run_tests: run_tests,
        run_benchmarks: run_benchmarks,
476
        ratchet_metrics: ratchet_metrics,
477
        ratchet_noise_percent: ratchet_noise_percent,
478
        save_metrics: save_metrics,
479
        test_shard: test_shard,
480 481
        logfile: logfile,
        nocapture: nocapture,
482
        color: color,
483 484 485
        show_boxplot: show_boxplot,
        boxplot_width: boxplot_width,
        show_all_stats: show_all_stats,
486
    };
487

B
Brian Anderson 已提交
488
    Some(Ok(test_opts))
489 490
}

491
pub fn opt_shard(maybestr: Option<String>) -> Option<(uint,uint)> {
492 493 494
    match maybestr {
        None => None,
        Some(s) => {
495
            let mut it = s.split('.');
A
Alex Crichton 已提交
496 497
            match (it.next().and_then(|s| s.parse::<uint>()),
                   it.next().and_then(|s| s.parse::<uint>()),
498 499 500
                   it.next()) {
                (Some(a), Some(b), None) => {
                    if a <= 0 || a > b {
S
Steve Klabnik 已提交
501
                        panic!("tried to run shard {a}.{b}, but {a} is out of bounds \
502 503 504 505
                              (should be between 1 and {b}", a=a, b=b)
                    }
                    Some((a, b))
                }
506
                _ => None,
507 508 509 510 511 512
            }
        }
    }
}


513
#[derive(Clone, PartialEq)]
514
pub struct BenchSamples {
515
    ns_iter_summ: stats::Summary<f64>,
516
    mb_s: uint,
517 518
}

519
#[derive(Clone, PartialEq)]
520 521 522 523 524
pub enum TestResult {
    TrOk,
    TrFailed,
    TrIgnored,
    TrMetrics(MetricMap),
525
    TrBench(BenchSamples),
526
}
527

A
Alex Crichton 已提交
528
enum OutputLocation<T> {
529
    Pretty(Box<term::Terminal<term::WriterWrapper> + Send>),
A
Alex Crichton 已提交
530 531 532
    Raw(T),
}

L
Luqman Aden 已提交
533 534
struct ConsoleTestState<T> {
    log_out: Option<File>,
A
Alex Crichton 已提交
535
    out: OutputLocation<T>,
536
    use_color: bool,
537 538 539
    show_boxplot: bool,
    boxplot_width: uint,
    show_all_stats: bool,
540 541 542 543
    total: uint,
    passed: uint,
    failed: uint,
    ignored: uint,
544
    measured: uint,
545
    metrics: MetricMap,
546
    failures: Vec<(TestDesc, Vec<u8> )> ,
547
    max_name_len: uint, // number of columns to fill when aligning names
548
}
549

L
Luqman Aden 已提交
550
impl<T: Writer> ConsoleTestState<T> {
A
Alex Crichton 已提交
551 552
    pub fn new(opts: &TestOpts,
               _: Option<T>) -> io::IoResult<ConsoleTestState<StdWriter>> {
553
        let log_out = match opts.logfile {
A
Alex Crichton 已提交
554
            Some(ref path) => Some(try!(File::create(path))),
555 556
            None => None
        };
C
Corey Richardson 已提交
557 558 559
        let out = match term::stdout() {
            None => Raw(io::stdio::stdout_raw()),
            Some(t) => Pretty(t)
560
        };
C
Corey Richardson 已提交
561

A
Alex Crichton 已提交
562
        Ok(ConsoleTestState {
563 564
            out: out,
            log_out: log_out,
565
            use_color: use_color(opts),
566 567 568
            show_boxplot: opts.show_boxplot,
            boxplot_width: opts.boxplot_width,
            show_all_stats: opts.show_all_stats,
569 570 571 572
            total: 0u,
            passed: 0u,
            failed: 0u,
            ignored: 0u,
573
            measured: 0u,
574
            metrics: MetricMap::new(),
575
            failures: Vec::new(),
576
            max_name_len: 0u,
A
Alex Crichton 已提交
577
        })
578 579
    }

A
Alex Crichton 已提交
580 581
    pub fn write_ok(&mut self) -> io::IoResult<()> {
        self.write_pretty("ok", term::color::GREEN)
582
    }
583

A
Alex Crichton 已提交
584 585
    pub fn write_failed(&mut self) -> io::IoResult<()> {
        self.write_pretty("FAILED", term::color::RED)
586 587
    }

A
Alex Crichton 已提交
588 589
    pub fn write_ignored(&mut self) -> io::IoResult<()> {
        self.write_pretty("ignored", term::color::YELLOW)
590
    }
591

A
Alex Crichton 已提交
592 593
    pub fn write_metric(&mut self) -> io::IoResult<()> {
        self.write_pretty("metric", term::color::CYAN)
594 595
    }

A
Alex Crichton 已提交
596 597
    pub fn write_bench(&mut self) -> io::IoResult<()> {
        self.write_pretty("bench", term::color::CYAN)
598
    }
599

A
Alex Crichton 已提交
600 601
    pub fn write_added(&mut self) -> io::IoResult<()> {
        self.write_pretty("added", term::color::GREEN)
602 603
    }

A
Alex Crichton 已提交
604 605
    pub fn write_improved(&mut self) -> io::IoResult<()> {
        self.write_pretty("improved", term::color::GREEN)
606 607
    }

A
Alex Crichton 已提交
608 609
    pub fn write_removed(&mut self) -> io::IoResult<()> {
        self.write_pretty("removed", term::color::YELLOW)
610 611
    }

A
Alex Crichton 已提交
612 613
    pub fn write_regressed(&mut self) -> io::IoResult<()> {
        self.write_pretty("regressed", term::color::RED)
614 615
    }

L
Luqman Aden 已提交
616
    pub fn write_pretty(&mut self,
617
                        word: &str,
A
Alex Crichton 已提交
618
                        color: term::color::Color) -> io::IoResult<()> {
L
Luqman Aden 已提交
619
        match self.out {
A
Alex Crichton 已提交
620
            Pretty(ref mut term) => {
621
                if self.use_color {
A
Alex Crichton 已提交
622
                    try!(term.fg(color));
623
                }
A
Alex Crichton 已提交
624
                try!(term.write(word.as_bytes()));
625
                if self.use_color {
A
Alex Crichton 已提交
626
                    try!(term.reset());
627
                }
A
Alex Crichton 已提交
628
                Ok(())
629
            }
A
Alex Crichton 已提交
630
            Raw(ref mut stdout) => stdout.write(word.as_bytes())
L
Luqman Aden 已提交
631 632 633
        }
    }

A
Alex Crichton 已提交
634
    pub fn write_plain(&mut self, s: &str) -> io::IoResult<()> {
L
Luqman Aden 已提交
635
        match self.out {
A
Alex Crichton 已提交
636 637
            Pretty(ref mut term) => term.write(s.as_bytes()),
            Raw(ref mut stdout) => stdout.write(s.as_bytes())
638 639 640
        }
    }

A
Alex Crichton 已提交
641
    pub fn write_run_start(&mut self, len: uint) -> io::IoResult<()> {
642
        self.total = len;
643
        let noun = if len != 1 { "tests" } else { "test" };
644
        self.write_plain(format!("\nrunning {} {}\n", len, noun).as_slice())
645 646
    }

A
Alex Crichton 已提交
647 648
    pub fn write_test_start(&mut self, test: &TestDesc,
                            align: NamePadding) -> io::IoResult<()> {
649
        let name = test.padded_name(self.max_name_len, align);
650
        self.write_plain(format!("test {} ... ", name).as_slice())
M
Marijn Haverbeke 已提交
651
    }
652

A
Alex Crichton 已提交
653
    pub fn write_result(&mut self, result: &TestResult) -> io::IoResult<()> {
A
Alex Crichton 已提交
654
        try!(match *result {
655 656 657
            TrOk => self.write_ok(),
            TrFailed => self.write_failed(),
            TrIgnored => self.write_ignored(),
658
            TrMetrics(ref mm) => {
A
Alex Crichton 已提交
659
                try!(self.write_metric());
660
                self.write_plain(format!(": {}", fmt_metrics(mm)).as_slice())
661
            }
662
            TrBench(ref bs) => {
A
Alex Crichton 已提交
663
                try!(self.write_bench());
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688

                if self.show_boxplot {
                    let mut wr = Vec::new();

                    try!(stats::write_boxplot(&mut wr, &bs.ns_iter_summ, self.boxplot_width));

                    let s = String::from_utf8(wr).unwrap();

                    try!(self.write_plain(format!(": {}", s).as_slice()));
                }

                if self.show_all_stats {
                    let mut wr = Vec::new();

                    try!(stats::write_5_number_summary(&mut wr, &bs.ns_iter_summ));

                    let s = String::from_utf8(wr).unwrap();

                    try!(self.write_plain(format!(": {}", s).as_slice()));
                } else {
                    try!(self.write_plain(format!(": {}",
                                                  fmt_bench_samples(bs)).as_slice()));
                }

                Ok(())
689
            }
A
Alex Crichton 已提交
690 691
        });
        self.write_plain("\n")
692
    }
693

A
Alex Crichton 已提交
694 695
    pub fn write_log(&mut self, test: &TestDesc,
                     result: &TestResult) -> io::IoResult<()> {
696
        match self.log_out {
A
Alex Crichton 已提交
697
            None => Ok(()),
L
Luqman Aden 已提交
698
            Some(ref mut o) => {
D
Derek Guenther 已提交
699
                let s = format!("{} {}\n", match *result {
700 701 702
                        TrOk => "ok".to_string(),
                        TrFailed => "failed".to_string(),
                        TrIgnored => "ignored".to_string(),
L
Luqman Aden 已提交
703 704
                        TrMetrics(ref mm) => fmt_metrics(mm),
                        TrBench(ref bs) => fmt_bench_samples(bs)
705
                    }, test.name.as_slice());
A
Alex Crichton 已提交
706
                o.write(s.as_bytes())
707 708
            }
        }
B
Brian Anderson 已提交
709 710
    }

A
Alex Crichton 已提交
711
    pub fn write_failures(&mut self) -> io::IoResult<()> {
A
Alex Crichton 已提交
712
        try!(self.write_plain("\nfailures:\n"));
713
        let mut failures = Vec::new();
714
        let mut fail_out = String::new();
715
        for &(ref f, ref stdout) in self.failures.iter() {
716
            failures.push(f.name.to_string());
717 718
            if stdout.len() > 0 {
                fail_out.push_str(format!("---- {} stdout ----\n\t",
719
                                          f.name.as_slice()).as_slice());
720
                let output = String::from_utf8_lossy(stdout.as_slice());
721
                fail_out.push_str(output.as_slice());
722 723 724 725
                fail_out.push_str("\n");
            }
        }
        if fail_out.len() > 0 {
A
Alex Crichton 已提交
726
            try!(self.write_plain("\n"));
727
            try!(self.write_plain(fail_out.as_slice()));
728
        }
729

A
Alex Crichton 已提交
730
        try!(self.write_plain("\nfailures:\n"));
731
        failures.sort();
D
Daniel Micay 已提交
732
        for name in failures.iter() {
733 734
            try!(self.write_plain(format!("    {}\n",
                                          name.as_slice()).as_slice()));
735
        }
A
Alex Crichton 已提交
736
        Ok(())
737 738
    }

A
Alex Crichton 已提交
739
    pub fn write_metric_diff(&mut self, diff: &MetricDiff) -> io::IoResult<()> {
740 741 742 743 744
        let mut noise = 0u;
        let mut improved = 0u;
        let mut regressed = 0u;
        let mut added = 0u;
        let mut removed = 0u;
745

D
Daniel Micay 已提交
746
        for (k, v) in diff.iter() {
747 748 749 750
            match *v {
                LikelyNoise => noise += 1,
                MetricAdded => {
                    added += 1;
A
Alex Crichton 已提交
751
                    try!(self.write_added());
752
                    try!(self.write_plain(format!(": {}\n", *k).as_slice()));
753 754 755
                }
                MetricRemoved => {
                    removed += 1;
A
Alex Crichton 已提交
756
                    try!(self.write_removed());
757
                    try!(self.write_plain(format!(": {}\n", *k).as_slice()));
758 759 760
                }
                Improvement(pct) => {
                    improved += 1;
761
                    try!(self.write_plain(format!(": {} ", *k).as_slice()));
A
Alex Crichton 已提交
762
                    try!(self.write_improved());
A
Alex Crichton 已提交
763
                    try!(self.write_plain(format!(" by {:.2}%\n",
764
                                                  pct as f64).as_slice()));
765 766 767
                }
                Regression(pct) => {
                    regressed += 1;
768
                    try!(self.write_plain(format!(": {} ", *k).as_slice()));
A
Alex Crichton 已提交
769
                    try!(self.write_regressed());
A
Alex Crichton 已提交
770
                    try!(self.write_plain(format!(" by {:.2}%\n",
771
                                                  pct as f64).as_slice()));
772 773 774
                }
            }
        }
A
Alex Crichton 已提交
775
        try!(self.write_plain(format!("result of ratchet: {} metrics added, \
A
Alex Crichton 已提交
776 777 778
                                        {} removed, {} improved, {} regressed, \
                                        {} noise\n",
                                       added, removed, improved, regressed,
779
                                       noise).as_slice()));
780
        if regressed == 0 {
A
Alex Crichton 已提交
781
            try!(self.write_plain("updated ratchet file\n"));
782
        } else {
A
Alex Crichton 已提交
783
            try!(self.write_plain("left ratchet file untouched\n"));
784
        }
A
Alex Crichton 已提交
785
        Ok(())
786 787
    }

L
Luqman Aden 已提交
788
    pub fn write_run_finish(&mut self,
789
                            ratchet_metrics: &Option<Path>,
A
Alex Crichton 已提交
790
                            ratchet_pct: Option<f64>) -> io::IoResult<bool> {
791
        assert!(self.passed + self.failed + self.ignored + self.measured == self.total);
792 793 794 795

        let ratchet_success = match *ratchet_metrics {
            None => true,
            Some(ref pth) => {
B
Brian Anderson 已提交
796
                try!(self.write_plain(format!("\nusing metrics ratchet: {}\n",
797
                                              pth.display()).as_slice()));
798 799 800
                match ratchet_pct {
                    None => (),
                    Some(pct) =>
A
Alex Crichton 已提交
801
                        try!(self.write_plain(format!("with noise-tolerance \
A
Alex Crichton 已提交
802
                                                         forced to: {}%\n",
803
                                                        pct).as_slice()))
804 805
                }
                let (diff, ok) = self.metrics.ratchet(pth, ratchet_pct);
A
Alex Crichton 已提交
806
                try!(self.write_metric_diff(&diff));
807 808 809 810 811 812
                ok
            }
        };

        let test_success = self.failed == 0u;
        if !test_success {
A
Alex Crichton 已提交
813
            try!(self.write_failures());
814
        }
815

816 817
        let success = ratchet_success && test_success;

A
Alex Crichton 已提交
818
        try!(self.write_plain("\ntest result: "));
819 820
        if success {
            // There's no parallelism at this point so it's safe to use color
A
Alex Crichton 已提交
821
            try!(self.write_ok());
822
        } else {
A
Alex Crichton 已提交
823
            try!(self.write_failed());
824
        }
L
Luqman Aden 已提交
825 826
        let s = format!(". {} passed; {} failed; {} ignored; {} measured\n\n",
                        self.passed, self.failed, self.ignored, self.measured);
827
        try!(self.write_plain(s.as_slice()));
A
Alex Crichton 已提交
828
        return Ok(success);
829
    }
830 831
}

832
pub fn fmt_metrics(mm: &MetricMap) -> String {
833
    let MetricMap(ref mm) = *mm;
834
    let v : Vec<String> = mm.iter()
A
Alex Crichton 已提交
835 836
        .map(|(k,v)| format!("{}: {} (+/- {})", *k,
                             v.value as f64, v.noise as f64))
837
        .collect();
838
    v.connect(", ")
839 840
}

841
pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
842
    if bs.mb_s != 0 {
A
Alex Crichton 已提交
843
        format!("{:>9} ns/iter (+/- {}) = {} MB/s",
844 845 846 847
             bs.ns_iter_summ.median as uint,
             (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint,
             bs.mb_s)
    } else {
A
Alex Crichton 已提交
848
        format!("{:>9} ns/iter (+/- {})",
849 850
             bs.ns_iter_summ.median as uint,
             (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint)
851
    }
852 853 854
}

// A simple console test runner
855 856 857
pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn> ) -> io::IoResult<bool> {

    fn callback<T: Writer>(event: &TestEvent, st: &mut ConsoleTestState<T>) -> io::IoResult<()> {
858
        match (*event).clone() {
859
            TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
860
            TeWait(ref test, padding) => st.write_test_start(test, padding),
861
            TeResult(test, result, stdout) => {
A
Alex Crichton 已提交
862 863
                try!(st.write_log(&test, &result));
                try!(st.write_result(&result));
864 865 866
                match result {
                    TrOk => st.passed += 1,
                    TrIgnored => st.ignored += 1,
867
                    TrMetrics(mm) => {
868
                        let tname = test.name.as_slice();
869
                        let MetricMap(mm) = mm;
D
Daniel Micay 已提交
870
                        for (k,v) in mm.iter() {
871
                            st.metrics
872 873 874
                              .insert_metric(format!("{}.{}",
                                                     tname,
                                                     k).as_slice(),
875 876
                                             v.value,
                                             v.noise);
877 878 879
                        }
                        st.measured += 1
                    }
880
                    TrBench(bs) => {
881
                        st.metrics.insert_metric(test.name.as_slice(),
882 883
                                                 bs.ns_iter_summ.median,
                                                 bs.ns_iter_summ.max - bs.ns_iter_summ.min);
884
                        st.measured += 1
885
                    }
886 887
                    TrFailed => {
                        st.failed += 1;
888
                        st.failures.push((test, stdout));
889 890
                    }
                }
A
Alex Crichton 已提交
891
                Ok(())
892 893
            }
        }
894
    }
895

A
Alex Crichton 已提交
896
    let mut st = try!(ConsoleTestState::new(opts, None::<StdWriter>));
897 898 899
    fn len_if_padded(t: &TestDescAndFn) -> uint {
        match t.testfn.padding() {
            PadNone => 0u,
900
            PadOnLeft | PadOnRight => t.desc.name.as_slice().len(),
901 902 903 904
        }
    }
    match tests.iter().max_by(|t|len_if_padded(*t)) {
        Some(t) => {
905
            let n = t.desc.name.as_slice();
906 907
            st.max_name_len = n.len();
        },
908 909
        None => {}
    }
A
Alex Crichton 已提交
910
    try!(run_tests(opts, tests, |x| callback(&x, &mut st)));
911 912 913
    match opts.save_metrics {
        None => (),
        Some(ref pth) => {
A
Alex Crichton 已提交
914 915
            try!(st.metrics.save(pth));
            try!(st.write_plain(format!("\nmetrics saved to: {}",
916
                                          pth.display()).as_slice()));
917 918
        }
    }
919
    return st.write_run_finish(&opts.ratchet_metrics, opts.ratchet_noise_percent);
920 921 922 923
}

#[test]
fn should_sort_failures_before_printing_them() {
A
Alex Crichton 已提交
924 925 926
    let test_a = TestDesc {
        name: StaticTestName("a"),
        ignore: false,
927
        should_fail: ShouldFail::No
A
Alex Crichton 已提交
928
    };
929

A
Alex Crichton 已提交
930 931 932
    let test_b = TestDesc {
        name: StaticTestName("b"),
        ignore: false,
933
        should_fail: ShouldFail::No
A
Alex Crichton 已提交
934
    };
935

L
Luqman Aden 已提交
936
    let mut st = ConsoleTestState {
A
Alex Crichton 已提交
937
        log_out: None,
D
Daniel Micay 已提交
938
        out: Raw(Vec::new()),
A
Alex Crichton 已提交
939
        use_color: false,
940 941 942
        show_boxplot: false,
        boxplot_width: 0,
        show_all_stats: false,
A
Alex Crichton 已提交
943 944 945 946 947 948 949
        total: 0u,
        passed: 0u,
        failed: 0u,
        ignored: 0u,
        measured: 0u,
        max_name_len: 10u,
        metrics: MetricMap::new(),
950
        failures: vec!((test_b, Vec::new()), (test_a, Vec::new()))
951
    };
952

953
    st.write_failures().unwrap();
L
Luqman Aden 已提交
954
    let s = match st.out {
D
Daniel Micay 已提交
955
        Raw(ref m) => String::from_utf8_lossy(m[]),
A
Alex Crichton 已提交
956
        Pretty(_) => unreachable!()
L
Luqman Aden 已提交
957
    };
A
Alex Crichton 已提交
958

959 960
    let apos = s.find_str("a").unwrap();
    let bpos = s.find_str("b").unwrap();
P
Patrick Walton 已提交
961
    assert!(apos < bpos);
962 963
}

964 965 966 967 968 969
fn use_color(opts: &TestOpts) -> bool {
    match opts.color {
        AutoColor => get_concurrency() == 1 && io::stdout().get_ref().isatty(),
        AlwaysColor => true,
        NeverColor => false,
    }
970
}
971

972
#[derive(Clone)]
B
Brian Anderson 已提交
973
enum TestEvent {
974
    TeFiltered(Vec<TestDesc> ),
975
    TeWait(TestDesc, NamePadding),
976
    TeResult(TestDesc, TestResult, Vec<u8> ),
B
Brian Anderson 已提交
977 978
}

979
pub type MonitorMsg = (TestDesc, TestResult, Vec<u8> );
980

F
Flavio Percoco 已提交
981
unsafe impl Send for MonitorMsg {}
F
Flavio Percoco 已提交
982

J
Jorge Aparicio 已提交
983 984 985 986 987
fn run_tests<F>(opts: &TestOpts,
                tests: Vec<TestDescAndFn> ,
                mut callback: F) -> io::IoResult<()> where
    F: FnMut(TestEvent) -> io::IoResult<()>,
{
T
Tim Chevalier 已提交
988
    let filtered_tests = filter_tests(opts, tests);
989 990 991
    let filtered_descs = filtered_tests.iter()
                                       .map(|t| t.desc.clone())
                                       .collect();
T
Tim Chevalier 已提交
992

A
Alex Crichton 已提交
993
    try!(callback(TeFiltered(filtered_descs)));
B
Brian Anderson 已提交
994

A
Aaron Turon 已提交
995 996
    let (filtered_tests, filtered_benchs_and_metrics): (Vec<_>, _) =
        filtered_tests.into_iter().partition(|e| {
997 998 999 1000 1001
            match e.testfn {
                StaticTestFn(_) | DynTestFn(_) => true,
                _ => false
            }
        });
1002

B
Brian Anderson 已提交
1003 1004
    // It's tempting to just spawn all the tests at once, but since we have
    // many tests that run in other processes we would be making a big mess.
B
Brian Anderson 已提交
1005
    let concurrency = get_concurrency();
1006

1007
    let mut remaining = filtered_tests;
1008
    remaining.reverse();
1009
    let mut pending = 0;
B
Brian Anderson 已提交
1010

1011
    let (tx, rx) = channel::<MonitorMsg>();
1012

1013 1014
    while pending > 0 || !remaining.is_empty() {
        while pending < concurrency && !remaining.is_empty() {
1015
            let test = remaining.pop().unwrap();
1016
            if concurrency == 1 {
1017 1018 1019
                // We are doing one test at a time so we can print the name
                // of the test before we run it. Useful for debugging tests
                // that hang forever.
A
Alex Crichton 已提交
1020
                try!(callback(TeWait(test.desc.clone(), test.testfn.padding())));
1021
            }
1022
            run_test(opts, !opts.run_tests, test, tx.clone());
1023
            pending += 1;
B
Brian Anderson 已提交
1024 1025
        }

1026
        let (desc, result, stdout) = rx.recv().unwrap();
1027
        if concurrency != 1 {
A
Alex Crichton 已提交
1028
            try!(callback(TeWait(desc.clone(), PadNone)));
1029
        }
A
Alex Crichton 已提交
1030
        try!(callback(TeResult(desc, result, stdout)));
1031
        pending -= 1;
B
Brian Anderson 已提交
1032
    }
1033 1034

    // All benchmarks run at the end, in serial.
1035
    // (this includes metric fns)
A
Aaron Turon 已提交
1036
    for b in filtered_benchs_and_metrics.into_iter() {
A
Alex Crichton 已提交
1037
        try!(callback(TeWait(b.desc.clone(), b.testfn.padding())));
1038
        run_test(opts, !opts.run_benchmarks, b, tx.clone());
1039
        let (test, result, stdout) = rx.recv().unwrap();
A
Alex Crichton 已提交
1040
        try!(callback(TeResult(test, result, stdout)));
1041
    }
A
Alex Crichton 已提交
1042
    Ok(())
B
Brian Anderson 已提交
1043 1044
}

1045
fn get_concurrency() -> uint {
1046
    use std::rt;
1047 1048
    match os::getenv("RUST_TEST_TASKS") {
        Some(s) => {
1049
            let opt_n: Option<uint> = FromStr::from_str(s.as_slice());
1050 1051
            match opt_n {
                Some(n) if n > 0 => n,
S
Steve Klabnik 已提交
1052
                _ => panic!("RUST_TEST_TASKS is `{}`, should be a positive integer.", s)
1053 1054 1055
            }
        }
        None => {
1056
            rt::default_sched_threads()
1057 1058
        }
    }
1059
}
1060

1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096
pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescAndFn> {
    let mut filtered = tests;

    // Remove tests that don't match the test filter
    filtered = match opts.filter {
        None => filtered,
        Some(ref re) => {
            filtered.into_iter()
                .filter(|test| re.is_match(test.desc.name.as_slice())).collect()
        }
    };

    // Maybe pull out the ignored test and unignore them
    filtered = if !opts.run_ignored {
        filtered
    } else {
        fn filter(test: TestDescAndFn) -> Option<TestDescAndFn> {
            if test.desc.ignore {
                let TestDescAndFn {desc, testfn} = test;
                Some(TestDescAndFn {
                    desc: TestDesc {ignore: false, ..desc},
                    testfn: testfn
                })
            } else {
                None
            }
        };
        filtered.into_iter().filter_map(|x| filter(x)).collect()
    };

    // Sort the tests alphabetically
    filtered.sort_by(|t1, t2| t1.desc.name.as_slice().cmp(t2.desc.name.as_slice()));

    // Shard the remaining tests, if sharding requested.
    match opts.test_shard {
        None => filtered,
1097
        Some((a,b)) => {
A
Aaron Turon 已提交
1098
            filtered.into_iter().enumerate()
1099 1100 1101
            // note: using a - 1 so that the valid shards, for example, are
            // 1.2 and 2.2 instead of 0.2 and 1.2
            .filter(|&(i,_)| i % b == (a - 1))
1102
            .map(|(_,t)| t)
1103 1104
            .collect()
        }
1105
    }
1106
}
1107

1108 1109
pub fn run_test(opts: &TestOpts,
                force_ignore: bool,
1110
                test: TestDescAndFn,
1111
                monitor_ch: Sender<MonitorMsg>) {
1112

1113 1114
    let TestDescAndFn {desc, testfn} = test;

1115
    if force_ignore || desc.ignore {
1116
        monitor_ch.send((desc, TrIgnored, Vec::new())).unwrap();
B
Brian Anderson 已提交
1117
        return;
1118 1119
    }

1120
    fn run_test_inner(desc: TestDesc,
1121
                      monitor_ch: Sender<MonitorMsg>,
1122
                      nocapture: bool,
1123
                      testfn: Thunk) {
A
Aaron Turon 已提交
1124
        Thread::spawn(move || {
1125 1126 1127 1128
            let (tx, rx) = channel();
            let mut reader = ChanReader::new(rx);
            let stdout = ChanWriter::new(tx.clone());
            let stderr = ChanWriter::new(tx);
1129
            let mut cfg = thread::Builder::new().name(match desc.name {
R
Richo Healey 已提交
1130 1131
                DynTestName(ref name) => name.clone().to_string(),
                StaticTestName(name) => name.to_string(),
1132
            });
1133 1134 1135
            if nocapture {
                drop((stdout, stderr));
            } else {
A
Aaron Turon 已提交
1136 1137
                cfg = cfg.stdout(box stdout as Box<Writer + Send>);
                cfg = cfg.stderr(box stderr as Box<Writer + Send>);
1138
            }
1139

1140
            let result_guard = cfg.spawn(move || { testfn.invoke(()) });
A
Aaron Turon 已提交
1141
            let stdout = reader.read_to_end().unwrap().into_iter().collect();
A
Aaron Turon 已提交
1142
            let test_result = calc_result(&desc, result_guard.join());
1143
            monitor_ch.send((desc.clone(), test_result, stdout)).unwrap();
1144
        }).detach();
1145 1146 1147
    }

    match testfn {
1148
        DynBenchFn(bencher) => {
L
Liigo Zhuang 已提交
1149
            let bs = ::bench::benchmark(|harness| bencher.run(harness));
1150
            monitor_ch.send((desc, TrBench(bs), Vec::new())).unwrap();
1151 1152 1153
            return;
        }
        StaticBenchFn(benchfn) => {
N
Niko Matsakis 已提交
1154
            let bs = ::bench::benchmark(|harness| (benchfn.clone())(harness));
1155
            monitor_ch.send((desc, TrBench(bs), Vec::new())).unwrap();
1156 1157
            return;
        }
1158 1159
        DynMetricFn(f) => {
            let mut mm = MetricMap::new();
1160
            f.invoke(&mut mm);
1161
            monitor_ch.send((desc, TrMetrics(mm), Vec::new())).unwrap();
1162 1163 1164 1165 1166
            return;
        }
        StaticMetricFn(f) => {
            let mut mm = MetricMap::new();
            f(&mut mm);
1167
            monitor_ch.send((desc, TrMetrics(mm), Vec::new())).unwrap();
1168 1169
            return;
        }
1170 1171
        DynTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture, f),
        StaticTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture,
1172
                                          Thunk::new(move|| f()))
1173
    }
1174 1175
}

1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
fn calc_result(desc: &TestDesc, task_result: Result<(), Box<Any+Send>>) -> TestResult {
    match (&desc.should_fail, task_result) {
        (&ShouldFail::No, Ok(())) |
        (&ShouldFail::Yes(None), Err(_)) => TrOk,
        (&ShouldFail::Yes(Some(msg)), Err(ref err))
            if err.downcast_ref::<String>()
                .map(|e| &**e)
                .or_else(|| err.downcast_ref::<&'static str>().map(|e| *e))
                .map(|e| e.contains(msg))
                .unwrap_or(false) => TrOk,
        _ => TrFailed,
1187
    }
1188 1189
}

1190 1191
impl MetricMap {

1192
    pub fn new() -> MetricMap {
A
Alexis Beingessner 已提交
1193
        MetricMap(BTreeMap::new())
1194 1195 1196
    }

    /// Load MetricDiff from a file.
A
Alex Crichton 已提交
1197
    ///
1198
    /// # Panics
A
Alex Crichton 已提交
1199
    ///
S
Steve Klabnik 已提交
1200
    /// This function will panic if the path does not exist or the path does not
A
Alex Crichton 已提交
1201
    /// contain a valid metric map.
1202
    pub fn load(p: &Path) -> MetricMap {
1203
        assert!(p.exists());
A
Alex Crichton 已提交
1204
        let mut f = File::open(p).unwrap();
1205
        let value = json::from_reader(&mut f as &mut io::Reader).unwrap();
F
Flavio Percoco 已提交
1206 1207 1208
        let mut decoder = json::Decoder::new(value);
        MetricMap(match Decodable::decode(&mut decoder) {
            Ok(t) => t,
S
Steve Klabnik 已提交
1209
            Err(e) => panic!("failure decoding JSON: {}", e)
F
Flavio Percoco 已提交
1210
        })
1211 1212 1213
    }

    /// Write MetricDiff to a file.
A
Alex Crichton 已提交
1214
    pub fn save(&self, p: &Path) -> io::IoResult<()> {
A
Alex Crichton 已提交
1215
        let mut file = try!(File::create(p));
1216
        let MetricMap(ref map) = *self;
1217
        write!(&mut file, "{}", json::as_json(map))
1218 1219
    }

1220 1221 1222 1223 1224 1225 1226
    /// Compare against another MetricMap. Optionally compare all
    /// measurements in the maps using the provided `noise_pct` as a
    /// percentage of each value to consider noise. If `None`, each
    /// measurement's noise threshold is independently chosen as the
    /// maximum of that measurement's recorded noise quantity in either
    /// map.
    pub fn compare_to_old(&self, old: &MetricMap,
1227
                          noise_pct: Option<f64>) -> MetricDiff {
A
Alexis Beingessner 已提交
1228
        let mut diff : MetricDiff = BTreeMap::new();
1229 1230
        let MetricMap(ref selfmap) = *self;
        let MetricMap(ref old) = *old;
D
Daniel Micay 已提交
1231
        for (k, vold) in old.iter() {
1232
            let r = match selfmap.get(k) {
1233 1234
                None => MetricRemoved,
                Some(v) => {
1235
                    let delta = v.value - vold.value;
1236
                    let noise = match noise_pct {
1237
                        None => vold.noise.abs().max(v.noise.abs()),
1238 1239
                        Some(pct) => vold.value * pct / 100.0
                    };
1240
                    if delta.abs() <= noise {
1241 1242
                        LikelyNoise
                    } else {
1243
                        let pct = delta.abs() / vold.value.max(f64::EPSILON) * 100.0;
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265
                        if vold.noise < 0.0 {
                            // When 'noise' is negative, it means we want
                            // to see deltas that go up over time, and can
                            // only tolerate slight negative movement.
                            if delta < 0.0 {
                                Regression(pct)
                            } else {
                                Improvement(pct)
                            }
                        } else {
                            // When 'noise' is positive, it means we want
                            // to see deltas that go down over time, and
                            // can only tolerate slight positive movements.
                            if delta < 0.0 {
                                Improvement(pct)
                            } else {
                                Regression(pct)
                            }
                        }
                    }
                }
            };
1266
            diff.insert((*k).clone(), r);
1267
        }
1268 1269
        let MetricMap(ref map) = *self;
        for (k, _) in map.iter() {
1270
            if !diff.contains_key(k) {
1271
                diff.insert((*k).clone(), MetricAdded);
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294
            }
        }
        diff
    }

    /// Insert a named `value` (+/- `noise`) metric into the map. The value
    /// must be non-negative. The `noise` indicates the uncertainty of the
    /// metric, which doubles as the "noise range" of acceptable
    /// pairwise-regressions on this named value, when comparing from one
    /// metric to the next using `compare_to_old`.
    ///
    /// If `noise` is positive, then it means this metric is of a value
    /// you want to see grow smaller, so a change larger than `noise` in the
    /// positive direction represents a regression.
    ///
    /// If `noise` is negative, then it means this metric is of a value
    /// you want to see grow larger, so a change larger than `noise` in the
    /// negative direction represents a regression.
    pub fn insert_metric(&mut self, name: &str, value: f64, noise: f64) {
        let m = Metric {
            value: value,
            noise: noise
        };
1295
        let MetricMap(ref mut map) = *self;
1296
        map.insert(name.to_string(), m);
1297 1298 1299 1300 1301 1302 1303 1304
    }

    /// Attempt to "ratchet" an external metric file. This involves loading
    /// metrics from a metric file (if it exists), comparing against
    /// the metrics in `self` using `compare_to_old`, and rewriting the
    /// file to contain the metrics in `self` if none of the
    /// `MetricChange`s are `Regression`. Returns the diff as well
    /// as a boolean indicating whether the ratchet succeeded.
1305
    pub fn ratchet(&self, p: &Path, pct: Option<f64>) -> (MetricDiff, bool) {
1306
        let old = if p.exists() {
1307 1308 1309 1310 1311
            MetricMap::load(p)
        } else {
            MetricMap::new()
        };

1312
        let diff : MetricDiff = self.compare_to_old(&old, pct);
1313
        let ok = diff.iter().all(|(_, v)| {
1314 1315 1316 1317
            match *v {
                Regression(_) => false,
                _ => true
            }
1318
        });
1319 1320

        if ok {
A
Alex Crichton 已提交
1321
            self.save(p).unwrap();
1322 1323 1324 1325 1326 1327 1328 1329
        }
        return (diff, ok)
    }
}


// Benchmarking

H
Huon Wilson 已提交
1330
/// A function that is opaque to the optimizer, to allow benchmarks to
1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341
/// pretend to use outputs to assist in avoiding dead-code
/// elimination.
///
/// This function is a no-op, and does not even read from `dummy`.
pub fn black_box<T>(dummy: T) {
    // we need to "use" the argument in some way LLVM can't
    // introspect.
    unsafe {asm!("" : : "r"(&dummy))}
}


1342
impl Bencher {
1343
    /// Callback for benchmark functions to run in their body.
J
Jorge Aparicio 已提交
1344
    pub fn iter<T, F>(&mut self, mut inner: F) where F: FnMut() -> T {
1345 1346 1347 1348 1349 1350
        self.dur = Duration::span(|| {
            let k = self.iterations;
            for _ in range(0u64, k) {
                black_box(inner());
            }
        });
1351
    }
1352

1353
    pub fn ns_elapsed(&mut self) -> u64 {
1354
        self.dur.num_nanoseconds().unwrap() as u64
1355
    }
1356

1357 1358 1359 1360
    pub fn ns_per_iter(&mut self) -> u64 {
        if self.iterations == 0 {
            0
        } else {
M
Michael Darakananda 已提交
1361
            self.ns_elapsed() / cmp::max(self.iterations, 1)
1362
        }
1363
    }
1364

J
Jorge Aparicio 已提交
1365
    pub fn bench_n<F>(&mut self, n: u64, f: F) where F: FnOnce(&mut Bencher) {
1366 1367 1368
        self.iterations = n;
        f(self);
    }
1369

1370
    // This is a more statistics-driven benchmark algorithm
J
Jorge Aparicio 已提交
1371
    pub fn auto_bench<F>(&mut self, mut f: F) -> stats::Summary<f64> where F: FnMut(&mut Bencher) {
1372 1373
        // Initial bench run to get ballpark figure.
        let mut n = 1_u64;
1374
        self.bench_n(n, |x| f(x));
1375

1376 1377 1378 1379 1380
        // Try to estimate iter count for 1ms falling back to 1m
        // iterations if first run took < 1ns.
        if self.ns_per_iter() == 0 {
            n = 1_000_000;
        } else {
M
Michael Darakananda 已提交
1381
            n = 1_000_000 / cmp::max(self.ns_per_iter(), 1);
1382
        }
1383 1384 1385 1386 1387 1388 1389
        // if the first run took more than 1ms we don't want to just
        // be left doing 0 iterations on every loop. The unfortunate
        // side effect of not being able to do as many runs is
        // automatically handled by the statistical analysis below
        // (i.e. larger error bars).
        if n == 0 { n = 1; }

1390
        let mut total_run = Duration::nanoseconds(0);
1391
        let samples : &mut [f64] = &mut [0.0_f64; 50];
1392
        loop {
1393 1394
            let mut summ = None;
            let mut summ5 = None;
1395

1396
            let loop_run = Duration::span(|| {
1397

1398 1399 1400 1401
                for p in samples.iter_mut() {
                    self.bench_n(n, |x| f(x));
                    *p = self.ns_per_iter() as f64;
                };
1402

1403 1404
                stats::winsorize(samples, 5.0);
                summ = Some(stats::Summary::new(samples));
1405

1406 1407 1408 1409
                for p in samples.iter_mut() {
                    self.bench_n(5 * n, |x| f(x));
                    *p = self.ns_per_iter() as f64;
                };
1410

1411 1412 1413 1414 1415
                stats::winsorize(samples, 5.0);
                summ5 = Some(stats::Summary::new(samples));
            });
            let summ = summ.unwrap();
            let summ5 = summ5.unwrap();
1416

1417
            // If we've run for 100ms and seem to have converged to a
1418
            // stable median.
1419
            if loop_run.num_milliseconds() > 100 &&
1420 1421 1422
                summ.median_abs_dev_pct < 1.0 &&
                summ.median - summ5.median < summ5.median_abs_dev {
                return summ5;
1423 1424
            }

1425
            total_run = total_run + loop_run;
1426
            // Longest we ever run for is 3s.
1427
            if total_run.num_seconds() > 3 {
1428
                return summ5;
1429
            }
1430

1431
            n *= 2;
1432 1433
        }
    }
1434 1435 1436
}

pub mod bench {
M
Michael Darakananda 已提交
1437
    use std::cmp;
1438
    use std::time::Duration;
1439
    use super::{Bencher, BenchSamples};
1440

J
Jorge Aparicio 已提交
1441
    pub fn benchmark<F>(f: F) -> BenchSamples where F: FnMut(&mut Bencher) {
1442
        let mut bs = Bencher {
1443
            iterations: 0,
1444
            dur: Duration::nanoseconds(0),
1445 1446 1447
            bytes: 0
        };

1448
        let ns_iter_summ = bs.auto_bench(f);
1449

M
Michael Darakananda 已提交
1450
        let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
1451
        let iter_s = 1_000_000_000 / ns_iter;
1452 1453 1454
        let mb_s = (bs.bytes * iter_s) / 1_000_000;

        BenchSamples {
1455
            ns_iter_summ: ns_iter_summ,
1456 1457 1458 1459 1460
            mb_s: mb_s as uint
        }
    }
}

1461 1462
#[cfg(test)]
mod tests {
1463
    use test::{TrFailed, TrIgnored, TrOk, filter_tests, parse_opts,
L
Liigo Zhuang 已提交
1464
               TestDesc, TestDescAndFn, TestOpts, run_test,
1465 1466
               Metric, MetricMap, MetricAdded, MetricRemoved,
               Improvement, Regression, LikelyNoise,
1467
               StaticTestName, DynTestName, DynTestFn, ShouldFail};
1468
    use std::io::TempDir;
1469
    use std::thunk::Thunk;
1470
    use std::sync::mpsc::channel;
1471

1472
    #[test]
1473
    pub fn do_not_run_ignored_tests() {
S
Steve Klabnik 已提交
1474
        fn f() { panic!(); }
1475 1476
        let desc = TestDescAndFn {
            desc: TestDesc {
1477
                name: StaticTestName("whatever"),
1478
                ignore: true,
1479
                should_fail: ShouldFail::No,
1480
            },
1481
            testfn: DynTestFn(Thunk::new(move|| f())),
1482
        };
1483
        let (tx, rx) = channel();
1484
        run_test(&TestOpts::new(), false, desc, tx);
1485
        let (_, res, _) = rx.recv().unwrap();
P
Patrick Walton 已提交
1486
        assert!(res != TrOk);
1487 1488 1489
    }

    #[test]
1490
    pub fn ignored_tests_result_in_ignored() {
1491
        fn f() { }
1492 1493
        let desc = TestDescAndFn {
            desc: TestDesc {
1494
                name: StaticTestName("whatever"),
1495
                ignore: true,
1496
                should_fail: ShouldFail::No,
1497
            },
1498
            testfn: DynTestFn(Thunk::new(move|| f())),
1499
        };
1500
        let (tx, rx) = channel();
1501
        run_test(&TestOpts::new(), false, desc, tx);
1502
        let (_, res, _) = rx.recv().unwrap();
1503
        assert!(res == TrIgnored);
1504 1505 1506
    }

    #[test]
1507
    fn test_should_fail() {
S
Steve Klabnik 已提交
1508
        fn f() { panic!(); }
1509 1510
        let desc = TestDescAndFn {
            desc: TestDesc {
1511
                name: StaticTestName("whatever"),
1512
                ignore: false,
1513 1514
                should_fail: ShouldFail::Yes(None)
            },
1515
            testfn: DynTestFn(Thunk::new(move|| f())),
1516 1517 1518
        };
        let (tx, rx) = channel();
        run_test(&TestOpts::new(), false, desc, tx);
1519
        let (_, res, _) = rx.recv().unwrap();
1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530
        assert!(res == TrOk);
    }

    #[test]
    fn test_should_fail_good_message() {
        fn f() { panic!("an error message"); }
        let desc = TestDescAndFn {
            desc: TestDesc {
                name: StaticTestName("whatever"),
                ignore: false,
                should_fail: ShouldFail::Yes(Some("error message"))
1531
            },
1532
            testfn: DynTestFn(Thunk::new(move|| f())),
1533
        };
1534
        let (tx, rx) = channel();
1535
        run_test(&TestOpts::new(), false, desc, tx);
1536
        let (_, res, _) = rx.recv().unwrap();
1537
        assert!(res == TrOk);
1538 1539
    }

1540 1541 1542 1543 1544 1545 1546 1547 1548
    #[test]
    fn test_should_fail_bad_message() {
        fn f() { panic!("an error message"); }
        let desc = TestDescAndFn {
            desc: TestDesc {
                name: StaticTestName("whatever"),
                ignore: false,
                should_fail: ShouldFail::Yes(Some("foobar"))
            },
1549
            testfn: DynTestFn(Thunk::new(move|| f())),
1550 1551 1552
        };
        let (tx, rx) = channel();
        run_test(&TestOpts::new(), false, desc, tx);
1553
        let (_, res, _) = rx.recv().unwrap();
1554 1555 1556
        assert!(res == TrFailed);
    }

1557
    #[test]
1558
    fn test_should_fail_but_succeeds() {
1559
        fn f() { }
1560 1561
        let desc = TestDescAndFn {
            desc: TestDesc {
1562
                name: StaticTestName("whatever"),
1563
                ignore: false,
1564
                should_fail: ShouldFail::Yes(None)
1565
            },
1566
            testfn: DynTestFn(Thunk::new(move|| f())),
1567
        };
1568
        let (tx, rx) = channel();
1569
        run_test(&TestOpts::new(), false, desc, tx);
1570
        let (_, res, _) = rx.recv().unwrap();
1571
        assert!(res == TrFailed);
1572 1573 1574
    }

    #[test]
1575
    fn first_free_arg_should_be_a_filter() {
1576
        let args = vec!("progname".to_string(), "some_regex_filter".to_string());
1577
        let opts = match parse_opts(args.as_slice()) {
B
Brian Anderson 已提交
1578
            Some(Ok(o)) => o,
S
Steve Klabnik 已提交
1579
            _ => panic!("Malformed arg in first_free_arg_should_be_a_filter")
B
Brian Anderson 已提交
1580
        };
1581
        assert!(opts.filter.expect("should've found filter").is_match("some_regex_filter"))
1582 1583 1584
    }

    #[test]
1585
    fn parse_ignored_flag() {
1586 1587 1588
        let args = vec!("progname".to_string(),
                        "filter".to_string(),
                        "--ignored".to_string());
1589
        let opts = match parse_opts(args.as_slice()) {
B
Brian Anderson 已提交
1590
            Some(Ok(o)) => o,
S
Steve Klabnik 已提交
1591
            _ => panic!("Malformed arg in parse_ignored_flag")
B
Brian Anderson 已提交
1592
        };
P
Patrick Walton 已提交
1593
        assert!((opts.run_ignored));
1594 1595 1596
    }

    #[test]
1597
    pub fn filter_for_ignored_option() {
1598 1599 1600
        // When we run ignored tests the test filter should filter out all the
        // unignored tests and flip the ignore flag on the rest to false

1601 1602 1603
        let mut opts = TestOpts::new();
        opts.run_tests = true;
        opts.run_ignored = true;
1604

1605
        let tests = vec!(
1606 1607
            TestDescAndFn {
                desc: TestDesc {
1608
                    name: StaticTestName("1"),
1609
                    ignore: true,
1610
                    should_fail: ShouldFail::No,
1611
                },
1612
                testfn: DynTestFn(Thunk::new(move|| {})),
1613
            },
1614 1615
            TestDescAndFn {
                desc: TestDesc {
1616
                    name: StaticTestName("2"),
1617
                    ignore: false,
1618
                    should_fail: ShouldFail::No,
1619
                },
1620
                testfn: DynTestFn(Thunk::new(move|| {})),
1621
            });
B
Brian Anderson 已提交
1622
        let filtered = filter_tests(&opts, tests);
1623

1624
        assert_eq!(filtered.len(), 1);
1625
        assert_eq!(filtered[0].desc.name.to_string(),
1626
                   "1");
1627
        assert!(filtered[0].desc.ignore == false);
1628 1629 1630
    }

    #[test]
1631
    pub fn sort_tests() {
1632 1633
        let mut opts = TestOpts::new();
        opts.run_tests = true;
1634 1635

        let names =
1636 1637 1638 1639 1640 1641 1642 1643 1644
            vec!("sha1::test".to_string(),
                 "int::test_to_str".to_string(),
                 "int::test_pow".to_string(),
                 "test::do_not_run_ignored_tests".to_string(),
                 "test::ignored_tests_result_in_ignored".to_string(),
                 "test::first_free_arg_should_be_a_filter".to_string(),
                 "test::parse_ignored_flag".to_string(),
                 "test::filter_for_ignored_option".to_string(),
                 "test::sort_tests".to_string());
1645 1646
        let tests =
        {
1647
            fn testfn() { }
1648
            let mut tests = Vec::new();
D
Daniel Micay 已提交
1649
            for name in names.iter() {
1650 1651
                let test = TestDescAndFn {
                    desc: TestDesc {
1652
                        name: DynTestName((*name).clone()),
1653
                        ignore: false,
1654
                        should_fail: ShouldFail::No,
1655
                    },
1656
                    testfn: DynTestFn(Thunk::new(testfn)),
1657
                };
L
Luqman Aden 已提交
1658
                tests.push(test);
1659
            }
L
Luqman Aden 已提交
1660
            tests
1661
        };
B
Brian Anderson 已提交
1662
        let filtered = filter_tests(&opts, tests);
1663

1664
        let expected =
1665 1666 1667 1668 1669 1670 1671 1672 1673
            vec!("int::test_pow".to_string(),
                 "int::test_to_str".to_string(),
                 "sha1::test".to_string(),
                 "test::do_not_run_ignored_tests".to_string(),
                 "test::filter_for_ignored_option".to_string(),
                 "test::first_free_arg_should_be_a_filter".to_string(),
                 "test::ignored_tests_result_in_ignored".to_string(),
                 "test::parse_ignored_flag".to_string(),
                 "test::sort_tests".to_string());
1674

1675
        for (a, b) in expected.iter().zip(filtered.iter()) {
1676
            assert!(*a == b.desc.name.to_string());
1677 1678
        }
    }
1679

1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692
    #[test]
    pub fn filter_tests_regex() {
        let mut opts = TestOpts::new();
        opts.filter = Some(::regex::Regex::new("a.*b.+c").unwrap());

        let mut names = ["yes::abXc", "yes::aXXXbXXXXc",
                         "no::XYZ", "no::abc"];
        names.sort();

        fn test_fn() {}
        let tests = names.iter().map(|name| {
            TestDescAndFn {
                desc: TestDesc {
1693
                    name: DynTestName(name.to_string()),
1694
                    ignore: false,
1695
                    should_fail: ShouldFail::No,
1696
                },
1697
                testfn: DynTestFn(Thunk::new(test_fn))
1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710
            }
        }).collect();
        let filtered = filter_tests(&opts, tests);

        let expected: Vec<&str> =
            names.iter().map(|&s| s).filter(|name| name.starts_with("yes")).collect();

        assert_eq!(filtered.len(), expected.len());
        for (test, expected_name) in filtered.iter().zip(expected.iter()) {
            assert_eq!(test.desc.name.as_slice(), *expected_name);
        }
    }

1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734
    #[test]
    pub fn test_metricmap_compare() {
        let mut m1 = MetricMap::new();
        let mut m2 = MetricMap::new();
        m1.insert_metric("in-both-noise", 1000.0, 200.0);
        m2.insert_metric("in-both-noise", 1100.0, 200.0);

        m1.insert_metric("in-first-noise", 1000.0, 2.0);
        m2.insert_metric("in-second-noise", 1000.0, 2.0);

        m1.insert_metric("in-both-want-downwards-but-regressed", 1000.0, 10.0);
        m2.insert_metric("in-both-want-downwards-but-regressed", 2000.0, 10.0);

        m1.insert_metric("in-both-want-downwards-and-improved", 2000.0, 10.0);
        m2.insert_metric("in-both-want-downwards-and-improved", 1000.0, 10.0);

        m1.insert_metric("in-both-want-upwards-but-regressed", 2000.0, -10.0);
        m2.insert_metric("in-both-want-upwards-but-regressed", 1000.0, -10.0);

        m1.insert_metric("in-both-want-upwards-and-improved", 1000.0, -10.0);
        m2.insert_metric("in-both-want-upwards-and-improved", 2000.0, -10.0);

        let diff1 = m2.compare_to_old(&m1, None);

1735 1736 1737 1738
        assert_eq!(*(diff1.get(&"in-both-noise".to_string()).unwrap()), LikelyNoise);
        assert_eq!(*(diff1.get(&"in-first-noise".to_string()).unwrap()), MetricRemoved);
        assert_eq!(*(diff1.get(&"in-second-noise".to_string()).unwrap()), MetricAdded);
        assert_eq!(*(diff1.get(&"in-both-want-downwards-but-regressed".to_string()).unwrap()),
1739
                   Regression(100.0));
1740
        assert_eq!(*(diff1.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1741
                   Improvement(50.0));
1742
        assert_eq!(*(diff1.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1743
                   Regression(50.0));
1744
        assert_eq!(*(diff1.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1745
                   Improvement(100.0));
1746 1747 1748 1749
        assert_eq!(diff1.len(), 7);

        let diff2 = m2.compare_to_old(&m1, Some(200.0));

1750 1751 1752 1753
        assert_eq!(*(diff2.get(&"in-both-noise".to_string()).unwrap()), LikelyNoise);
        assert_eq!(*(diff2.get(&"in-first-noise".to_string()).unwrap()), MetricRemoved);
        assert_eq!(*(diff2.get(&"in-second-noise".to_string()).unwrap()), MetricAdded);
        assert_eq!(*(diff2.get(&"in-both-want-downwards-but-regressed".to_string()).unwrap()),
1754
                   LikelyNoise);
1755
        assert_eq!(*(diff2.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1756
                   LikelyNoise);
1757
        assert_eq!(*(diff2.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1758
                   LikelyNoise);
1759
        assert_eq!(*(diff2.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1760
                   LikelyNoise);
1761 1762 1763
        assert_eq!(diff2.len(), 7);
    }

1764
    #[test]
1765 1766
    pub fn ratchet_test() {

1767
        let dpth = TempDir::new("test-ratchet").ok().expect("missing test for ratchet");
1768
        let pth = dpth.path().join("ratchet.json");
1769 1770 1771 1772 1773 1774 1775 1776 1777

        let mut m1 = MetricMap::new();
        m1.insert_metric("runtime", 1000.0, 2.0);
        m1.insert_metric("throughput", 50.0, 2.0);

        let mut m2 = MetricMap::new();
        m2.insert_metric("runtime", 1100.0, 2.0);
        m2.insert_metric("throughput", 50.0, 2.0);

1778
        m1.save(&pth).unwrap();
1779 1780 1781 1782 1783

        // Ask for a ratchet that should fail to advance.
        let (diff1, ok1) = m2.ratchet(&pth, None);
        assert_eq!(ok1, false);
        assert_eq!(diff1.len(), 2);
1784 1785
        assert_eq!(*(diff1.get(&"runtime".to_string()).unwrap()), Regression(10.0));
        assert_eq!(*(diff1.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1786 1787 1788

        // Check that it was not rewritten.
        let m3 = MetricMap::load(&pth);
1789
        let MetricMap(m3) = m3;
1790
        assert_eq!(m3.len(), 2);
1791 1792
        assert_eq!(*(m3.get(&"runtime".to_string()).unwrap()), Metric::new(1000.0, 2.0));
        assert_eq!(*(m3.get(&"throughput".to_string()).unwrap()), Metric::new(50.0, 2.0));
1793 1794 1795 1796 1797 1798

        // Ask for a ratchet with an explicit noise-percentage override,
        // that should advance.
        let (diff2, ok2) = m2.ratchet(&pth, Some(10.0));
        assert_eq!(ok2, true);
        assert_eq!(diff2.len(), 2);
1799 1800
        assert_eq!(*(diff2.get(&"runtime".to_string()).unwrap()), LikelyNoise);
        assert_eq!(*(diff2.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1801 1802 1803

        // Check that it was rewritten.
        let m4 = MetricMap::load(&pth);
1804
        let MetricMap(m4) = m4;
1805
        assert_eq!(m4.len(), 2);
1806 1807
        assert_eq!(*(m4.get(&"runtime".to_string()).unwrap()), Metric::new(1100.0, 2.0));
        assert_eq!(*(m4.get(&"throughput".to_string()).unwrap()), Metric::new(50.0, 2.0));
1808
    }
1809
}