lib.rs 61.3 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]
B
Brian Anderson 已提交
28
#![staged_api]
29 30 31 32
#![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",
33
       html_root_url = "http://doc.rust-lang.org/nightly/")]
A
Alex Crichton 已提交
34
#![feature(asm, slicing_syntax)]
L
Liigo Zhuang 已提交
35

A
Alex Crichton 已提交
36
extern crate getopts;
37
extern crate regex;
L
Liigo Zhuang 已提交
38
extern crate serialize;
39
extern crate "serialize" as rustc_serialize;
A
Alex Crichton 已提交
40
extern crate term;
41

S
Steven Fackler 已提交
42 43 44 45 46 47 48 49 50
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::*;

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

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

L
Liigo Zhuang 已提交
77 78
// to be used by rustc to compile tests in libtest
pub mod test {
79
    pub use {Bencher, TestName, TestResult, TestDesc,
L
Liigo Zhuang 已提交
80 81 82 83 84
             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,
85
             parse_opts, StaticBenchFn, ShouldFail};
L
Liigo Zhuang 已提交
86 87
}

88 89
pub mod stats;

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

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

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

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

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

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

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

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

184 185 186 187 188
/// 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`.
189
#[derive(Copy)]
190
pub struct Bencher {
191
    iterations: u64,
192
    dur: Duration,
193
    pub bytes: u64,
194
}
195

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

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

211
#[derive(Show)]
212
pub struct TestDescAndFn {
213 214
    pub desc: TestDesc,
    pub testfn: TestFn,
215 216
}

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

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

229
#[derive(PartialEq)]
A
Alexis Beingessner 已提交
230
pub struct MetricMap(BTreeMap<String,Metric>);
231

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

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

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

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

267
// A variant optimized for invocation with a static test vector.
S
Steve Klabnik 已提交
268
// This will panic (intentionally) when fed any dynamic tests, because
269 270 271 272 273
// 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 &[].
274
pub fn test_main_static(args: &[String], tests: &[TestDescAndFn]) {
275
    let owned_tests = tests.iter().map(|t| {
276
        match t.testfn {
277 278
            StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: t.desc.clone() },
            StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: t.desc.clone() },
S
Steve Klabnik 已提交
279
            _ => panic!("non-static tests passed to test::test_main_static")
280
        }
281
    }).collect();
282 283 284
    test_main(args, owned_tests)
}

285
#[derive(Copy)]
286 287 288 289 290 291
pub enum ColorConfig {
    AutoColor,
    AlwaysColor,
    NeverColor,
}

292
pub struct TestOpts {
293
    pub filter: Option<Regex>,
294 295 296 297 298 299 300
    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)>,
301 302
    pub logfile: Option<Path>,
    pub nocapture: bool,
303
    pub color: ColorConfig,
304 305 306
    pub show_boxplot: bool,
    pub boxplot_width: uint,
    pub show_all_stats: bool,
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
}

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,
323
            color: AutoColor,
324 325 326
            show_boxplot: false,
            boxplot_width: 50,
            show_all_stats: false,
327 328
        }
    }
329
}
330

331
/// Result of parsing the options.
332
pub type OptRes = Result<TestOpts, String>;
333

334 335
fn optgroups() -> Vec<getopts::OptGroup> {
    vec!(getopts::optflag("", "ignored", "Run ignored tests"),
336 337 338 339
      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",
340
                     "PATH"),
341
      getopts::optopt("", "ratchet-metrics",
342 343 344
                     "Location to load and save metrics from. The metrics \
                      loaded are cause benchmarks to fail if they run too \
                      slowly", "PATH"),
345
      getopts::optopt("", "ratchet-noise-percent",
346 347
                     "Tests within N% of the recorded metrics will be \
                      considered as passing", "PERCENTAGE"),
348
      getopts::optopt("", "logfile", "Write logs to the specified file instead \
349
                          of stdout", "PATH"),
350
      getopts::optopt("", "test-shard", "run shard A, of B shards, worth of the testsuite",
351 352
                     "A.B"),
      getopts::optflag("", "nocapture", "don't capture stdout/stderr of each \
353 354 355 356
                                         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;
357 358 359 360
            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"))
361 362
}

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

The FILTER regex is tested against the name of all tests to run, and
368
only those tests that match are run.
369 370

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

373 374 375 376
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.

377 378
Test Attributes:

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

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

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

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

416
    let run_ignored = matches.opt_present("ignored");
417

418
    let logfile = matches.opt_str("logfile");
419
    let logfile = logfile.map(|s| Path::new(s));
420

421
    let run_benchmarks = matches.opt_present("bench");
422
    let run_tests = ! run_benchmarks ||
423
        matches.opt_present("test");
424

425
    let ratchet_metrics = matches.opt_str("ratchet-metrics");
426
    let ratchet_metrics = ratchet_metrics.map(|s| Path::new(s));
427

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

432
    let save_metrics = matches.opt_str("save-metrics");
433
    let save_metrics = save_metrics.map(|s| Path::new(s));
434

435
    let test_shard = matches.opt_str("test-shard");
436
    let test_shard = opt_shard(test_shard);
437

438 439 440 441 442
    let mut nocapture = matches.opt_present("nocapture");
    if !nocapture {
        nocapture = os::getenv("RUST_TEST_NOCAPTURE").is_some();
    }

443 444 445 446 447 448 449 450 451 452
    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))),
    };

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
    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");

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

B
Brian Anderson 已提交
485
    Some(Ok(test_opts))
486 487
}

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                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(())
686
            }
A
Alex Crichton 已提交
687 688
        });
        self.write_plain("\n")
689
    }
690

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

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

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

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

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

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

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

        let test_success = self.failed == 0u;
        if !test_success {
A
Alex Crichton 已提交
810
            try!(self.write_failures());
811
        }
812

813 814
        let success = ratchet_success && test_success;

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

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

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

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

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

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

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

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

950
    st.write_failures().unwrap();
L
Luqman Aden 已提交
951
    let s = match st.out {
952
        Raw(ref m) => String::from_utf8_lossy(m.index(&FullRange)),
A
Alex Crichton 已提交
953
        Pretty(_) => unreachable!()
L
Luqman Aden 已提交
954
    };
A
Alex Crichton 已提交
955

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

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

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

976
pub type MonitorMsg = (TestDesc, TestResult, Vec<u8> );
977

F
Flavio Percoco 已提交
978
unsafe impl Send for MonitorMsg {}
F
Flavio Percoco 已提交
979

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

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

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

B
Brian Anderson 已提交
1000 1001
    // 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 已提交
1002
    let concurrency = get_concurrency();
1003

1004
    let mut remaining = filtered_tests;
1005
    remaining.reverse();
1006
    let mut pending = 0;
B
Brian Anderson 已提交
1007

1008
    let (tx, rx) = channel::<MonitorMsg>();
1009

1010 1011
    while pending > 0 || !remaining.is_empty() {
        while pending < concurrency && !remaining.is_empty() {
1012
            let test = remaining.pop().unwrap();
1013
            if concurrency == 1 {
1014 1015 1016
                // 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 已提交
1017
                try!(callback(TeWait(test.desc.clone(), test.testfn.padding())));
1018
            }
1019
            run_test(opts, !opts.run_tests, test, tx.clone());
1020
            pending += 1;
B
Brian Anderson 已提交
1021 1022
        }

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

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

1042
fn get_concurrency() -> uint {
1043
    use std::rt;
1044 1045
    match os::getenv("RUST_TEST_TASKS") {
        Some(s) => {
1046
            let opt_n: Option<uint> = FromStr::from_str(s.as_slice());
1047 1048
            match opt_n {
                Some(n) if n > 0 => n,
S
Steve Klabnik 已提交
1049
                _ => panic!("RUST_TEST_TASKS is `{}`, should be a positive integer.", s)
1050 1051 1052
            }
        }
        None => {
1053
            rt::default_sched_threads()
1054 1055
        }
    }
1056
}
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
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,
1094
        Some((a,b)) => {
A
Aaron Turon 已提交
1095
            filtered.into_iter().enumerate()
1096 1097 1098
            // 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))
1099
            .map(|(_,t)| t)
1100 1101
            .collect()
        }
1102
    }
1103
}
1104

1105 1106
pub fn run_test(opts: &TestOpts,
                force_ignore: bool,
1107
                test: TestDescAndFn,
1108
                monitor_ch: Sender<MonitorMsg>) {
1109

1110 1111
    let TestDescAndFn {desc, testfn} = test;

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

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

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

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

1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
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,
1184
    }
1185 1186
}

1187 1188
impl MetricMap {

1189
    pub fn new() -> MetricMap {
A
Alexis Beingessner 已提交
1190
        MetricMap(BTreeMap::new())
1191 1192 1193
    }

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

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

1217 1218 1219 1220 1221 1222 1223
    /// 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,
1224
                          noise_pct: Option<f64>) -> MetricDiff {
A
Alexis Beingessner 已提交
1225
        let mut diff : MetricDiff = BTreeMap::new();
1226 1227
        let MetricMap(ref selfmap) = *self;
        let MetricMap(ref old) = *old;
D
Daniel Micay 已提交
1228
        for (k, vold) in old.iter() {
1229
            let r = match selfmap.get(k) {
1230 1231
                None => MetricRemoved,
                Some(v) => {
1232
                    let delta = v.value - vold.value;
1233
                    let noise = match noise_pct {
1234
                        None => vold.noise.abs().max(v.noise.abs()),
1235 1236
                        Some(pct) => vold.value * pct / 100.0
                    };
1237
                    if delta.abs() <= noise {
1238 1239
                        LikelyNoise
                    } else {
1240
                        let pct = delta.abs() / vold.value.max(f64::EPSILON) * 100.0;
1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262
                        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)
                            }
                        }
                    }
                }
            };
1263
            diff.insert((*k).clone(), r);
1264
        }
1265 1266
        let MetricMap(ref map) = *self;
        for (k, _) in map.iter() {
1267
            if !diff.contains_key(k) {
1268
                diff.insert((*k).clone(), MetricAdded);
1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
            }
        }
        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
        };
1292
        let MetricMap(ref mut map) = *self;
1293
        map.insert(name.to_string(), m);
1294 1295 1296 1297 1298 1299 1300 1301
    }

    /// 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.
1302
    pub fn ratchet(&self, p: &Path, pct: Option<f64>) -> (MetricDiff, bool) {
1303
        let old = if p.exists() {
1304 1305 1306 1307 1308
            MetricMap::load(p)
        } else {
            MetricMap::new()
        };

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

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


// Benchmarking

H
Huon Wilson 已提交
1327
/// A function that is opaque to the optimizer, to allow benchmarks to
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338
/// 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))}
}


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

1350
    pub fn ns_elapsed(&mut self) -> u64 {
1351
        self.dur.num_nanoseconds().unwrap() as u64
1352
    }
1353

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

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

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

1373 1374 1375 1376 1377
        // 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 已提交
1378
            n = 1_000_000 / cmp::max(self.ns_per_iter(), 1);
1379
        }
1380 1381 1382 1383 1384 1385 1386
        // 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; }

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

1393
            let loop_run = Duration::span(|| {
1394

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

1400 1401
                stats::winsorize(samples, 5.0);
                summ = Some(stats::Summary::new(samples));
1402

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

1408 1409 1410 1411 1412
                stats::winsorize(samples, 5.0);
                summ5 = Some(stats::Summary::new(samples));
            });
            let summ = summ.unwrap();
            let summ5 = summ5.unwrap();
1413

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

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

1428
            n *= 2;
1429 1430
        }
    }
1431 1432 1433
}

pub mod bench {
M
Michael Darakananda 已提交
1434
    use std::cmp;
1435
    use std::time::Duration;
1436
    use super::{Bencher, BenchSamples};
1437

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

1445
        let ns_iter_summ = bs.auto_bench(f);
1446

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

        BenchSamples {
1452
            ns_iter_summ: ns_iter_summ,
1453 1454 1455 1456 1457
            mb_s: mb_s as uint
        }
    }
}

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

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

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

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

1537 1538 1539 1540 1541 1542 1543 1544 1545
    #[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"))
            },
1546
            testfn: DynTestFn(Thunk::new(move|| f())),
1547 1548 1549
        };
        let (tx, rx) = channel();
        run_test(&TestOpts::new(), false, desc, tx);
1550
        let (_, res, _) = rx.recv().unwrap();
1551 1552 1553
        assert!(res == TrFailed);
    }

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

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

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

    #[test]
1594
    pub fn filter_for_ignored_option() {
1595 1596 1597
        // 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

1598 1599 1600
        let mut opts = TestOpts::new();
        opts.run_tests = true;
        opts.run_ignored = true;
1601

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

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

    #[test]
1628
    pub fn sort_tests() {
1629 1630
        let mut opts = TestOpts::new();
        opts.run_tests = true;
1631 1632

        let names =
1633 1634 1635 1636 1637 1638 1639 1640 1641
            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());
1642 1643
        let tests =
        {
1644
            fn testfn() { }
1645
            let mut tests = Vec::new();
D
Daniel Micay 已提交
1646
            for name in names.iter() {
1647 1648
                let test = TestDescAndFn {
                    desc: TestDesc {
1649
                        name: DynTestName((*name).clone()),
1650
                        ignore: false,
1651
                        should_fail: ShouldFail::No,
1652
                    },
1653
                    testfn: DynTestFn(Thunk::new(testfn)),
1654
                };
L
Luqman Aden 已提交
1655
                tests.push(test);
1656
            }
L
Luqman Aden 已提交
1657
            tests
1658
        };
B
Brian Anderson 已提交
1659
        let filtered = filter_tests(&opts, tests);
1660

1661
        let expected =
1662 1663 1664 1665 1666 1667 1668 1669 1670
            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());
1671

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

1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689
    #[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 {
1690
                    name: DynTestName(name.to_string()),
1691
                    ignore: false,
1692
                    should_fail: ShouldFail::No,
1693
                },
1694
                testfn: DynTestFn(Thunk::new(test_fn))
1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707
            }
        }).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);
        }
    }

1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731
    #[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);

1732 1733 1734 1735
        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()),
1736
                   Regression(100.0));
1737
        assert_eq!(*(diff1.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1738
                   Improvement(50.0));
1739
        assert_eq!(*(diff1.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1740
                   Regression(50.0));
1741
        assert_eq!(*(diff1.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1742
                   Improvement(100.0));
1743 1744 1745 1746
        assert_eq!(diff1.len(), 7);

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

1747 1748 1749 1750
        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()),
1751
                   LikelyNoise);
1752
        assert_eq!(*(diff2.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1753
                   LikelyNoise);
1754
        assert_eq!(*(diff2.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1755
                   LikelyNoise);
1756
        assert_eq!(*(diff2.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1757
                   LikelyNoise);
1758 1759 1760
        assert_eq!(diff2.len(), 7);
    }

1761
    #[test]
1762 1763
    pub fn ratchet_test() {

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

        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);

1775
        m1.save(&pth).unwrap();
1776 1777 1778 1779 1780

        // 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);
1781 1782
        assert_eq!(*(diff1.get(&"runtime".to_string()).unwrap()), Regression(10.0));
        assert_eq!(*(diff1.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1783 1784 1785

        // Check that it was not rewritten.
        let m3 = MetricMap::load(&pth);
1786
        let MetricMap(m3) = m3;
1787
        assert_eq!(m3.len(), 2);
1788 1789
        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));
1790 1791 1792 1793 1794 1795

        // 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);
1796 1797
        assert_eq!(*(diff2.get(&"runtime".to_string()).unwrap()), LikelyNoise);
        assert_eq!(*(diff2.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1798 1799 1800

        // Check that it was rewritten.
        let m4 = MetricMap::load(&pth);
1801
        let MetricMap(m4) = m4;
1802
        assert_eq!(m4.len(), 2);
1803 1804
        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));
1805
    }
1806
}