lib.rs 61.2 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;
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 std::any::{Any, AnyRefExt};
A
Alexis Beingessner 已提交
52
use std::collections::BTreeMap;
53
use stats::Stats;
L
Liigo Zhuang 已提交
54
use getopts::{OptGroup, optflag, optopt};
55
use regex::Regex;
56
use serialize::{json, Decodable, Encodable};
L
Liigo Zhuang 已提交
57 58
use term::Terminal;
use term::color::{Color, RED, YELLOW, GREEN, CYAN};
59

M
Michael Darakananda 已提交
60
use std::cmp;
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;
68
use std::num::{Float, FloatMath, Int};
69
use std::os;
B
Brendan Zabarauskas 已提交
70
use std::str::FromStr;
71
use std::string::String;
A
Aaron Turon 已提交
72
use std::thread::{mod, Thread};
73
use std::time::Duration;
74
use std::thunk::{Thunk, Invoke};
75

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

87 88
pub mod stats;

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

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

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

impl Copy for NamePadding {}
121 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());
126
        let mut pad = " ".repeat(fill);
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, Encodable, Decodable, PartialEq, Show)]
219
pub struct Metric {
220 221
    value: f64,
    noise: f64
222 223
}

N
Niko Matsakis 已提交
224 225
impl Copy for Metric {}

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

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

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

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

N
Niko Matsakis 已提交
252 253
impl Copy for MetricChange {}

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

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

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

290 291 292 293 294 295
pub enum ColorConfig {
    AutoColor,
    AlwaysColor,
    NeverColor,
}

N
Niko Matsakis 已提交
296 297
impl Copy for ColorConfig {}

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

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,
329
            color: AutoColor,
330 331 332
            show_boxplot: false,
            boxplot_width: 50,
            show_all_stats: false,
333 334
        }
    }
335
}
336

337
/// Result of parsing the options.
338
pub type OptRes = Result<TestOpts, String>;
339

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

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

The FILTER regex is tested against the name of all tests to run, and
374
only those tests that match are run.
375 376

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

379 380 381 382
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.

383 384
Test Attributes:

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

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

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

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

422
    let run_ignored = matches.opt_present("ignored");
423

424
    let logfile = matches.opt_str("logfile");
425
    let logfile = logfile.map(|s| Path::new(s));
426

427
    let run_benchmarks = matches.opt_present("bench");
428
    let run_tests = ! run_benchmarks ||
429
        matches.opt_present("test");
430

431
    let ratchet_metrics = matches.opt_str("ratchet-metrics");
432
    let ratchet_metrics = ratchet_metrics.map(|s| Path::new(s));
433

434
    let ratchet_noise_percent = matches.opt_str("ratchet-noise-percent");
435 436
    let ratchet_noise_percent =
        ratchet_noise_percent.map(|s| from_str::<f64>(s.as_slice()).unwrap());
437

438
    let save_metrics = matches.opt_str("save-metrics");
439
    let save_metrics = save_metrics.map(|s| Path::new(s));
440

441
    let test_shard = matches.opt_str("test-shard");
442
    let test_shard = opt_shard(test_shard);
443

444 445 446 447 448
    let mut nocapture = matches.opt_present("nocapture");
    if !nocapture {
        nocapture = os::getenv("RUST_TEST_NOCAPTURE").is_some();
    }

449 450 451 452 453 454 455 456 457 458
    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))),
    };

459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
    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");

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

B
Brian Anderson 已提交
491
    Some(Ok(test_opts))
492 493
}

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                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(())
691
            }
A
Alex Crichton 已提交
692 693
        });
        self.write_plain("\n")
694
    }
695

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

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

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

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

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

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

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

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

818 819
        let success = ratchet_success && test_success;

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

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

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

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

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

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

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

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

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

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

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

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

981
pub type MonitorMsg = (TestDesc, TestResult, Vec<u8> );
982

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

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

995
    let (filtered_tests, filtered_benchs_and_metrics) =
996 997 998 999 1000 1001
        filtered_tests.partition(|e| {
            match e.testfn {
                StaticTestFn(_) | DynTestFn(_) => true,
                _ => false
            }
        });
1002

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1190 1191
impl MetricMap {

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

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

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

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

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

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

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


// Benchmarking

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


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

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

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

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

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

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

1391
        let mut total_run = Duration::nanoseconds(0);
N
Nick Cameron 已提交
1392
        let samples : &mut [f64] = &mut [0.0_f64, ..50];
1393
        loop {
1394 1395
            let mut summ = None;
            let mut summ5 = None;
1396

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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