lib.rs 61.5 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"]
B
Brian Anderson 已提交
27
#![unstable]
B
Brian Anderson 已提交
28
#![staged_api]
29 30 31 32
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
33
       html_root_url = "http://doc.rust-lang.org/nightly/")]
34
#![allow(unknown_features)]
A
Alex Crichton 已提交
35
#![feature(asm, slicing_syntax)]
36
#![feature(box_syntax)]
H
Huon Wilson 已提交
37
#![allow(unknown_features)] #![feature(int_uint)]
38
#![allow(unstable)]
L
Liigo Zhuang 已提交
39

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

S
Steven Fackler 已提交
46 47 48 49 50 51 52 53 54
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::*;

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

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

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

92 93
pub mod stats;

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

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

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

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

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

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

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

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

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

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

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

215 216
unsafe impl Send for TestDesc {}

217
#[derive(Show)]
218
pub struct TestDescAndFn {
219 220
    pub desc: TestDesc,
    pub testfn: TestFn,
221 222
}

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

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

235
#[derive(PartialEq)]
A
Alexis Beingessner 已提交
236
pub struct MetricMap(BTreeMap<String,Metric>);
237

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

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

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

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

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

291
#[derive(Copy)]
292 293 294 295 296 297
pub enum ColorConfig {
    AutoColor,
    AlwaysColor,
    NeverColor,
}

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),
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
    let ratchet_noise_percent =
A
Alex Crichton 已提交
436
        ratchet_noise_percent.map(|s| s.as_slice().parse::<f64>().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('.');
A
Alex Crichton 已提交
499 500
            match (it.next().and_then(|s| s.parse::<uint>()),
                   it.next().and_then(|s| s.parse::<uint>()),
501 502 503
                   it.next()) {
                (Some(a), Some(b), None) => {
                    if a <= 0 || a > b {
S
Steve Klabnik 已提交
504
                        panic!("tried to run shard {a}.{b}, but {a} is out of bounds \
505 506 507 508
                              (should be between 1 and {b}", a=a, b=b)
                    }
                    Some((a, b))
                }
509
                _ => None,
510 511 512 513 514 515
            }
        }
    }
}


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

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

531 532
unsafe impl Send for TestResult {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        let test_success = self.failed == 0u;
        if !test_success {
A
Alex Crichton 已提交
818
            try!(self.write_failures());
819
        }
820

821 822
        let success = ratchet_success && test_success;

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

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

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

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

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

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

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

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

958
    st.write_failures().unwrap();
L
Luqman Aden 已提交
959
    let s = match st.out {
J
Jorge Aparicio 已提交
960
        Raw(ref m) => String::from_utf8_lossy(&m[]),
A
Alex Crichton 已提交
961
        Pretty(_) => unreachable!()
L
Luqman Aden 已提交
962
    };
A
Alex Crichton 已提交
963

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

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

977
#[derive(Clone)]
B
Brian Anderson 已提交
978
enum TestEvent {
979
    TeFiltered(Vec<TestDesc> ),
980
    TeWait(TestDesc, NamePadding),
981
    TeResult(TestDesc, TestResult, Vec<u8> ),
B
Brian Anderson 已提交
982 983
}

984
pub type MonitorMsg = (TestDesc, TestResult, Vec<u8> );
985

F
Flavio Percoco 已提交
986

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

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

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

B
Brian Anderson 已提交
1007 1008
    // 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 已提交
1009
    let concurrency = get_concurrency();
1010

1011
    let mut remaining = filtered_tests;
1012
    remaining.reverse();
1013
    let mut pending = 0;
B
Brian Anderson 已提交
1014

1015
    let (tx, rx) = channel::<MonitorMsg>();
1016

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

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

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

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

1112 1113
pub fn run_test(opts: &TestOpts,
                force_ignore: bool,
1114
                test: TestDescAndFn,
1115
                monitor_ch: Sender<MonitorMsg>) {
1116

1117 1118
    let TestDescAndFn {desc, testfn} = test;

1119
    if force_ignore || desc.ignore {
1120
        monitor_ch.send((desc, TrIgnored, Vec::new())).unwrap();
B
Brian Anderson 已提交
1121
        return;
1122 1123
    }

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

A
Aaron Turon 已提交
1144
            let result_guard = cfg.scoped(move || { testfn.invoke(()) });
A
Aaron Turon 已提交
1145
            let stdout = reader.read_to_end().unwrap().into_iter().collect();
A
Aaron Turon 已提交
1146
            let test_result = calc_result(&desc, result_guard.join());
1147
            monitor_ch.send((desc.clone(), test_result, stdout)).unwrap();
A
Aaron Turon 已提交
1148
        });
1149 1150 1151
    }

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

1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
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,
1191
    }
1192 1193
}

1194 1195
impl MetricMap {

1196
    pub fn new() -> MetricMap {
A
Alexis Beingessner 已提交
1197
        MetricMap(BTreeMap::new())
1198 1199 1200
    }

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

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

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

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

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

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


// Benchmarking

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


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

1358
    pub fn ns_elapsed(&mut self) -> u64 {
1359
        self.dur.num_nanoseconds().unwrap() as u64
1360
    }
1361

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

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

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

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

1395
        let mut total_run = Duration::nanoseconds(0);
1396
        let samples : &mut [f64] = &mut [0.0_f64; 50];
1397
        loop {
1398 1399
            let mut summ = None;
            let mut summ5 = None;
1400

1401
            let loop_run = Duration::span(|| {
1402

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

1408 1409
                stats::winsorize(samples, 5.0);
                summ = Some(stats::Summary::new(samples));
1410

1411 1412 1413 1414
                for p in samples.iter_mut() {
                    self.bench_n(5 * n, |x| f(x));
                    *p = self.ns_per_iter() as f64;
                };
1415

1416 1417 1418 1419 1420
                stats::winsorize(samples, 5.0);
                summ5 = Some(stats::Summary::new(samples));
            });
            let summ = summ.unwrap();
            let summ5 = summ5.unwrap();
1421

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

1430
            total_run = total_run + loop_run;
1431
            // Longest we ever run for is 3s.
1432
            if total_run.num_seconds() > 3 {
1433
                return summ5;
1434
            }
1435

1436
            n *= 2;
1437 1438
        }
    }
1439 1440 1441
}

pub mod bench {
M
Michael Darakananda 已提交
1442
    use std::cmp;
1443
    use std::time::Duration;
1444
    use super::{Bencher, BenchSamples};
1445

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

1453
        let ns_iter_summ = bs.auto_bench(f);
1454

M
Michael Darakananda 已提交
1455
        let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
1456
        let iter_s = 1_000_000_000 / ns_iter;
1457 1458 1459
        let mb_s = (bs.bytes * iter_s) / 1_000_000;

        BenchSamples {
1460
            ns_iter_summ: ns_iter_summ,
1461 1462 1463 1464 1465
            mb_s: mb_s as uint
        }
    }
}

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

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

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

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

1545 1546 1547 1548 1549 1550 1551 1552 1553
    #[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"))
            },
1554
            testfn: DynTestFn(Thunk::new(move|| f())),
1555 1556 1557
        };
        let (tx, rx) = channel();
        run_test(&TestOpts::new(), false, desc, tx);
1558
        let (_, res, _) = rx.recv().unwrap();
1559 1560 1561
        assert!(res == TrFailed);
    }

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

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

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

    #[test]
1602
    pub fn filter_for_ignored_option() {
1603 1604 1605
        // 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

1606 1607 1608
        let mut opts = TestOpts::new();
        opts.run_tests = true;
        opts.run_ignored = true;
1609

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

1629
        assert_eq!(filtered.len(), 1);
1630
        assert_eq!(filtered[0].desc.name.to_string(),
1631
                   "1");
1632
        assert!(filtered[0].desc.ignore == false);
1633 1634 1635
    }

    #[test]
1636
    pub fn sort_tests() {
1637 1638
        let mut opts = TestOpts::new();
        opts.run_tests = true;
1639 1640

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

1669
        let expected =
1670 1671 1672 1673 1674 1675 1676 1677 1678
            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());
1679

1680
        for (a, b) in expected.iter().zip(filtered.iter()) {
1681
            assert!(*a == b.desc.name.to_string());
1682 1683
        }
    }
1684

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

1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739
    #[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);

1740 1741 1742 1743
        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()),
1744
                   Regression(100.0));
1745
        assert_eq!(*(diff1.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1746
                   Improvement(50.0));
1747
        assert_eq!(*(diff1.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1748
                   Regression(50.0));
1749
        assert_eq!(*(diff1.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1750
                   Improvement(100.0));
1751 1752 1753 1754
        assert_eq!(diff1.len(), 7);

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

1755 1756 1757 1758
        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()),
1759
                   LikelyNoise);
1760
        assert_eq!(*(diff2.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1761
                   LikelyNoise);
1762
        assert_eq!(*(diff2.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1763
                   LikelyNoise);
1764
        assert_eq!(*(diff2.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1765
                   LikelyNoise);
1766 1767 1768
        assert_eq!(diff2.len(), 7);
    }

1769
    #[test]
1770 1771
    pub fn ratchet_test() {

1772
        let dpth = TempDir::new("test-ratchet").ok().expect("missing test for ratchet");
1773
        let pth = dpth.path().join("ratchet.json");
1774 1775 1776 1777 1778 1779 1780 1781 1782

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

1783
        m1.save(&pth).unwrap();
1784 1785 1786 1787 1788

        // 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);
1789 1790
        assert_eq!(*(diff1.get(&"runtime".to_string()).unwrap()), Regression(10.0));
        assert_eq!(*(diff1.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1791 1792 1793

        // Check that it was not rewritten.
        let m3 = MetricMap::load(&pth);
1794
        let MetricMap(m3) = m3;
1795
        assert_eq!(m3.len(), 2);
1796 1797
        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));
1798 1799 1800 1801 1802 1803

        // 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);
1804 1805
        assert_eq!(*(diff2.get(&"runtime".to_string()).unwrap()), LikelyNoise);
        assert_eq!(*(diff2.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1806 1807 1808

        // Check that it was rewritten.
        let m4 = MetricMap::load(&pth);
1809
        let MetricMap(m4) = m4;
1810
        assert_eq!(m4.len(), 2);
1811 1812
        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));
1813
    }
1814
}