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]
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

D
Daniel Micay 已提交
34
#![feature(asm, macro_rules, phase, globs, slicing_syntax)]
35
#![feature(unboxed_closures, default_type_params)]
L
Liigo Zhuang 已提交
36

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

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

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

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

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

89 90
pub mod stats;

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

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

J
Jorge Aparicio 已提交
115
#[deriving(Clone, Copy)]
N
Niko Matsakis 已提交
116 117 118 119 120 121
enum NamePadding {
    PadNone,
    PadOnLeft,
    PadOnRight,
}

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

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

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

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

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

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

N
Niko Matsakis 已提交
197
#[deriving(Copy, Clone, Show, PartialEq, Eq, Hash)]
198 199 200 201 202
pub enum ShouldFail {
    No,
    Yes(Option<&'static str>)
}

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

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

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

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

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

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

240
/// Analysis of a single change in metric
J
Jorge Aparicio 已提交
241
#[deriving(Copy, PartialEq, Show)]
242 243 244 245 246 247 248 249
pub enum MetricChange {
    LikelyNoise,
    MetricAdded,
    MetricRemoved,
    Improvement(f64),
    Regression(f64)
}

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

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

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

J
Jorge Aparicio 已提交
286
#[deriving(Copy)]
287 288 289 290 291 292
pub enum ColorConfig {
    AutoColor,
    AlwaysColor,
    NeverColor,
}

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

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

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

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

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

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

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

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

378 379
Test Attributes:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

814 815
        let success = ratchet_success && test_success;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1188 1189
impl MetricMap {

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

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

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

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

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

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

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


// Benchmarking

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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