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

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

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

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

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

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

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

50
use std::collections::TreeMap;
51
use stats::Stats;
L
Liigo Zhuang 已提交
52
use getopts::{OptGroup, optflag, optopt};
53
use regex::Regex;
A
Alex Crichton 已提交
54
use serialize::{json, Decodable};
S
Sean McArthur 已提交
55
use serialize::json::{Json, ToJson};
L
Liigo Zhuang 已提交
56 57
use term::Terminal;
use term::color::{Color, RED, YELLOW, GREEN, CYAN};
58

M
Michael Darakananda 已提交
59
use std::cmp;
60
use std::f64;
61
use std::fmt::Show;
62
use std::fmt;
63
use std::io::fs::PathExtensions;
L
Luqman Aden 已提交
64
use std::io::stdio::StdWriter;
65
use std::io::{File, ChanReader, ChanWriter};
66
use std::io;
67
use std::num::{Float, FloatMath, Int};
68
use std::os;
B
Brendan Zabarauskas 已提交
69
use std::str::FromStr;
70
use std::string::String;
71
use std::task::TaskBuilder;
72
use std::time::Duration;
73

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

85 86
pub mod stats;

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

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

111 112 113 114
#[deriving(Clone)]
enum NamePadding { PadNone, PadOnLeft, PadOnRight }

impl TestDesc {
115 116
    fn padded_name(&self, column_count: uint, align: NamePadding) -> String {
        let mut name = String::from_str(self.name.as_slice());
117
        let fill = column_count.saturating_sub(name.len());
118
        let mut pad = " ".repeat(fill);
119
        match align {
120
            PadNone => name,
121 122
            PadOnLeft => {
                pad.push_str(name.as_slice());
123
                pad
124 125 126
            }
            PadOnRight => {
                name.push_str(pad.as_slice());
127
                name
128
            }
129 130 131 132
        }
    }
}

133 134
/// Represents a benchmark function.
pub trait TDynBenchFn {
135
    fn run(&self, harness: &mut Bencher);
136 137
}

138
// A function that runs a test. If the function returns successfully,
S
Steve Klabnik 已提交
139
// the test succeeds; if the function panics then the test fails. We
140 141
// may need to come up with a more clever definition of test in order
// to support isolation of tests into tasks.
142
pub enum TestFn {
143
    StaticTestFn(fn()),
144
    StaticBenchFn(fn(&mut Bencher)),
145
    StaticMetricFn(proc(&mut MetricMap):'static),
A
Alex Crichton 已提交
146
    DynTestFn(proc():Send),
147 148
    DynMetricFn(proc(&mut MetricMap):'static),
    DynBenchFn(Box<TDynBenchFn+'static>)
149 150
}

151 152 153
impl TestFn {
    fn padding(&self) -> NamePadding {
        match self {
A
Alex Crichton 已提交
154 155 156 157 158 159
            &StaticTestFn(..)   => PadNone,
            &StaticBenchFn(..)  => PadOnRight,
            &StaticMetricFn(..) => PadOnRight,
            &DynTestFn(..)      => PadNone,
            &DynMetricFn(..)    => PadOnRight,
            &DynBenchFn(..)     => PadOnRight,
160 161 162 163
        }
    }
}

164 165 166 167 168 169 170 171 172 173 174 175 176
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())
    }
}

177 178 179 180 181
/// 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`.
182
pub struct Bencher {
183
    iterations: u64,
184
    dur: Duration,
185
    pub bytes: u64,
186
}
187 188 189

// The definition of a single test. A test runner will run a list of
// these.
190
#[deriving(Clone, Show, PartialEq, Eq, Hash)]
191
pub struct TestDesc {
192 193 194
    pub name: TestName,
    pub ignore: bool,
    pub should_fail: bool,
195
}
196

197
#[deriving(Show)]
198
pub struct TestDescAndFn {
199 200
    pub desc: TestDesc,
    pub testfn: TestFn,
201 202
}

203
#[deriving(Clone, Encodable, Decodable, PartialEq, Show)]
204
pub struct Metric {
205 206
    value: f64,
    noise: f64
207 208
}

L
Liigo Zhuang 已提交
209 210 211 212 213 214
impl Metric {
    pub fn new(value: f64, noise: f64) -> Metric {
        Metric {value: value, noise: noise}
    }
}

215
#[deriving(PartialEq)]
216
pub struct MetricMap(TreeMap<String,Metric>);
217

218
impl Clone for MetricMap {
219
    fn clone(&self) -> MetricMap {
220 221
        let MetricMap(ref map) = *self;
        MetricMap(map.clone())
222 223 224
    }
}

225
/// Analysis of a single change in metric
226
#[deriving(PartialEq, Show)]
227 228 229 230 231 232 233 234
pub enum MetricChange {
    LikelyNoise,
    MetricAdded,
    MetricRemoved,
    Improvement(f64),
    Regression(f64)
}

235
pub type MetricDiff = TreeMap<String,MetricChange>;
236

237
// The default console test runner. It accepts the command line
238
// arguments and a vector of test_descs.
239
pub fn test_main(args: &[String], tests: Vec<TestDescAndFn> ) {
M
Marijn Haverbeke 已提交
240
    let opts =
241
        match parse_opts(args) {
B
Brian Anderson 已提交
242
            Some(Ok(o)) => o,
S
Steve Klabnik 已提交
243
            Some(Err(msg)) => panic!("{}", msg),
B
Brian Anderson 已提交
244
            None => return
M
Marijn Haverbeke 已提交
245
        };
A
Alex Crichton 已提交
246 247
    match run_tests_console(&opts, tests) {
        Ok(true) => {}
S
Steve Klabnik 已提交
248 249
        Ok(false) => panic!("Some tests failed"),
        Err(e) => panic!("io error when running tests: {}", e),
A
Alex Crichton 已提交
250
    }
251 252
}

253
// A variant optimized for invocation with a static test vector.
S
Steve Klabnik 已提交
254
// This will panic (intentionally) when fed any dynamic tests, because
255 256 257 258 259
// 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 &[].
260
pub fn test_main_static(args: &[String], tests: &[TestDescAndFn]) {
261
    let owned_tests = tests.iter().map(|t| {
262
        match t.testfn {
263 264
            StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: t.desc.clone() },
            StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: t.desc.clone() },
S
Steve Klabnik 已提交
265
            _ => panic!("non-static tests passed to test::test_main_static")
266
        }
267
    }).collect();
268 269 270
    test_main(args, owned_tests)
}

271 272 273 274 275 276
pub enum ColorConfig {
    AutoColor,
    AlwaysColor,
    NeverColor,
}

277
pub struct TestOpts {
278
    pub filter: Option<Regex>,
279 280 281 282 283 284 285
    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)>,
286 287
    pub logfile: Option<Path>,
    pub nocapture: bool,
288
    pub color: ColorConfig,
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
}

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,
305
            color: AutoColor,
306 307
        }
    }
308
}
309

310
/// Result of parsing the options.
311
pub type OptRes = Result<TestOpts, String>;
312

313 314
fn optgroups() -> Vec<getopts::OptGroup> {
    vec!(getopts::optflag("", "ignored", "Run ignored tests"),
315 316 317 318
      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",
319
                     "PATH"),
320
      getopts::optopt("", "ratchet-metrics",
321 322 323
                     "Location to load and save metrics from. The metrics \
                      loaded are cause benchmarks to fail if they run too \
                      slowly", "PATH"),
324
      getopts::optopt("", "ratchet-noise-percent",
325 326
                     "Tests within N% of the recorded metrics will be \
                      considered as passing", "PERCENTAGE"),
327
      getopts::optopt("", "logfile", "Write logs to the specified file instead \
328
                          of stdout", "PATH"),
329
      getopts::optopt("", "test-shard", "run shard A, of B shards, worth of the testsuite",
330 331
                     "A.B"),
      getopts::optflag("", "nocapture", "don't capture stdout/stderr of each \
332 333 334 335 336
                                         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;
            never  = never colorize output;", "auto|always|never"))
337 338
}

339
fn usage(binary: &str) {
A
Alex Crichton 已提交
340
    let message = format!("Usage: {} [OPTIONS] [FILTER]", binary);
341 342 343
    println!(r"{usage}

The FILTER regex is tested against the name of all tests to run, and
344
only those tests that match are run.
345 346

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

349 350 351 352
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.

353 354
Test Attributes:

A
Alex Crichton 已提交
355
    #[test]        - Indicates a function is a test to be run. This function
356
                     takes no arguments.
A
Alex Crichton 已提交
357
    #[bench]       - Indicates a function is a benchmark to be run. This
358
                     function takes one argument (test::Bencher).
A
Alex Crichton 已提交
359
    #[should_fail] - This function (also labeled with #[test]) will only pass if
S
Steve Klabnik 已提交
360
                     the code causes a failure (an assertion failure or panic!)
A
Alex Crichton 已提交
361
    #[ignore]      - When applied to a function which is already attributed as a
362 363
                     test, then the test runner will ignore these tests during
                     normal test runs. Running with --ignored will run these
S
Steven Fackler 已提交
364
                     tests.",
365 366
             usage = getopts::usage(message.as_slice(),
                                    optgroups().as_slice()));
367 368
}

369
// Parses command line arguments into test options
370
pub fn parse_opts(args: &[String]) -> Option<OptRes> {
371
    let args_ = args.tail();
372
    let matches =
373
        match getopts::getopts(args_.as_slice(), optgroups().as_slice()) {
L
Luqman Aden 已提交
374
          Ok(m) => m,
375
          Err(f) => return Some(Err(f.to_string()))
M
Marijn Haverbeke 已提交
376 377
        };

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

380
    let filter = if matches.free.len() > 0 {
N
Nick Cameron 已提交
381
        let s = matches.free[0].as_slice();
382 383
        match Regex::new(s) {
            Ok(re) => Some(re),
A
Alex Crichton 已提交
384
            Err(e) => return Some(Err(format!("could not parse /{}/: {}", s, e)))
385 386 387 388
        }
    } else {
        None
    };
389

390
    let run_ignored = matches.opt_present("ignored");
391

392
    let logfile = matches.opt_str("logfile");
393
    let logfile = logfile.map(|s| Path::new(s));
394

395
    let run_benchmarks = matches.opt_present("bench");
396
    let run_tests = ! run_benchmarks ||
397
        matches.opt_present("test");
398

399
    let ratchet_metrics = matches.opt_str("ratchet-metrics");
400
    let ratchet_metrics = ratchet_metrics.map(|s| Path::new(s));
401

402
    let ratchet_noise_percent = matches.opt_str("ratchet-noise-percent");
403 404
    let ratchet_noise_percent =
        ratchet_noise_percent.map(|s| from_str::<f64>(s.as_slice()).unwrap());
405

406
    let save_metrics = matches.opt_str("save-metrics");
407
    let save_metrics = save_metrics.map(|s| Path::new(s));
408

409
    let test_shard = matches.opt_str("test-shard");
410
    let test_shard = opt_shard(test_shard);
411

412 413 414 415 416
    let mut nocapture = matches.opt_present("nocapture");
    if !nocapture {
        nocapture = os::getenv("RUST_TEST_NOCAPTURE").is_some();
    }

417 418 419 420 421 422 423 424 425 426
    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))),
    };

427 428 429
    let test_opts = TestOpts {
        filter: filter,
        run_ignored: run_ignored,
430 431
        run_tests: run_tests,
        run_benchmarks: run_benchmarks,
432
        ratchet_metrics: ratchet_metrics,
433
        ratchet_noise_percent: ratchet_noise_percent,
434
        save_metrics: save_metrics,
435
        test_shard: test_shard,
436 437
        logfile: logfile,
        nocapture: nocapture,
438
        color: color,
439
    };
440

B
Brian Anderson 已提交
441
    Some(Ok(test_opts))
442 443
}

444
pub fn opt_shard(maybestr: Option<String>) -> Option<(uint,uint)> {
445 446 447
    match maybestr {
        None => None,
        Some(s) => {
448
            let mut it = s.as_slice().split('.');
449 450 451 452
            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 已提交
453
                        panic!("tried to run shard {a}.{b}, but {a} is out of bounds \
454 455 456 457
                              (should be between 1 and {b}", a=a, b=b)
                    }
                    Some((a, b))
                }
458
                _ => None,
459 460 461 462 463 464
            }
        }
    }
}


465
#[deriving(Clone, PartialEq)]
466
pub struct BenchSamples {
467
    ns_iter_summ: stats::Summary<f64>,
468
    mb_s: uint,
469 470
}

471
#[deriving(Clone, PartialEq)]
472 473 474 475 476
pub enum TestResult {
    TrOk,
    TrFailed,
    TrIgnored,
    TrMetrics(MetricMap),
477
    TrBench(BenchSamples),
478
}
479

A
Alex Crichton 已提交
480
enum OutputLocation<T> {
481
    Pretty(Box<term::Terminal<term::WriterWrapper> + Send>),
A
Alex Crichton 已提交
482 483 484
    Raw(T),
}

L
Luqman Aden 已提交
485 486
struct ConsoleTestState<T> {
    log_out: Option<File>,
A
Alex Crichton 已提交
487
    out: OutputLocation<T>,
488
    use_color: bool,
489 490 491 492
    total: uint,
    passed: uint,
    failed: uint,
    ignored: uint,
493
    measured: uint,
494
    metrics: MetricMap,
495
    failures: Vec<(TestDesc, Vec<u8> )> ,
496
    max_name_len: uint, // number of columns to fill when aligning names
497
}
498

L
Luqman Aden 已提交
499
impl<T: Writer> ConsoleTestState<T> {
A
Alex Crichton 已提交
500 501
    pub fn new(opts: &TestOpts,
               _: Option<T>) -> io::IoResult<ConsoleTestState<StdWriter>> {
502
        let log_out = match opts.logfile {
A
Alex Crichton 已提交
503
            Some(ref path) => Some(try!(File::create(path))),
504 505
            None => None
        };
C
Corey Richardson 已提交
506 507 508
        let out = match term::stdout() {
            None => Raw(io::stdio::stdout_raw()),
            Some(t) => Pretty(t)
509
        };
C
Corey Richardson 已提交
510

A
Alex Crichton 已提交
511
        Ok(ConsoleTestState {
512 513
            out: out,
            log_out: log_out,
514
            use_color: use_color(opts),
515 516 517 518
            total: 0u,
            passed: 0u,
            failed: 0u,
            ignored: 0u,
519
            measured: 0u,
520
            metrics: MetricMap::new(),
521
            failures: Vec::new(),
522
            max_name_len: 0u,
A
Alex Crichton 已提交
523
        })
524 525
    }

A
Alex Crichton 已提交
526 527
    pub fn write_ok(&mut self) -> io::IoResult<()> {
        self.write_pretty("ok", term::color::GREEN)
528
    }
529

A
Alex Crichton 已提交
530 531
    pub fn write_failed(&mut self) -> io::IoResult<()> {
        self.write_pretty("FAILED", term::color::RED)
532 533
    }

A
Alex Crichton 已提交
534 535
    pub fn write_ignored(&mut self) -> io::IoResult<()> {
        self.write_pretty("ignored", term::color::YELLOW)
536
    }
537

A
Alex Crichton 已提交
538 539
    pub fn write_metric(&mut self) -> io::IoResult<()> {
        self.write_pretty("metric", term::color::CYAN)
540 541
    }

A
Alex Crichton 已提交
542 543
    pub fn write_bench(&mut self) -> io::IoResult<()> {
        self.write_pretty("bench", term::color::CYAN)
544
    }
545

A
Alex Crichton 已提交
546 547
    pub fn write_added(&mut self) -> io::IoResult<()> {
        self.write_pretty("added", term::color::GREEN)
548 549
    }

A
Alex Crichton 已提交
550 551
    pub fn write_improved(&mut self) -> io::IoResult<()> {
        self.write_pretty("improved", term::color::GREEN)
552 553
    }

A
Alex Crichton 已提交
554 555
    pub fn write_removed(&mut self) -> io::IoResult<()> {
        self.write_pretty("removed", term::color::YELLOW)
556 557
    }

A
Alex Crichton 已提交
558 559
    pub fn write_regressed(&mut self) -> io::IoResult<()> {
        self.write_pretty("regressed", term::color::RED)
560 561
    }

L
Luqman Aden 已提交
562
    pub fn write_pretty(&mut self,
563
                        word: &str,
A
Alex Crichton 已提交
564
                        color: term::color::Color) -> io::IoResult<()> {
L
Luqman Aden 已提交
565
        match self.out {
A
Alex Crichton 已提交
566
            Pretty(ref mut term) => {
567
                if self.use_color {
A
Alex Crichton 已提交
568
                    try!(term.fg(color));
569
                }
A
Alex Crichton 已提交
570
                try!(term.write(word.as_bytes()));
571
                if self.use_color {
A
Alex Crichton 已提交
572
                    try!(term.reset());
573
                }
A
Alex Crichton 已提交
574
                Ok(())
575
            }
A
Alex Crichton 已提交
576
            Raw(ref mut stdout) => stdout.write(word.as_bytes())
L
Luqman Aden 已提交
577 578 579
        }
    }

A
Alex Crichton 已提交
580
    pub fn write_plain(&mut self, s: &str) -> io::IoResult<()> {
L
Luqman Aden 已提交
581
        match self.out {
A
Alex Crichton 已提交
582 583
            Pretty(ref mut term) => term.write(s.as_bytes()),
            Raw(ref mut stdout) => stdout.write(s.as_bytes())
584 585 586
        }
    }

A
Alex Crichton 已提交
587
    pub fn write_run_start(&mut self, len: uint) -> io::IoResult<()> {
588
        self.total = len;
589
        let noun = if len != 1 { "tests" } else { "test" };
590
        self.write_plain(format!("\nrunning {} {}\n", len, noun).as_slice())
591 592
    }

A
Alex Crichton 已提交
593 594
    pub fn write_test_start(&mut self, test: &TestDesc,
                            align: NamePadding) -> io::IoResult<()> {
595
        let name = test.padded_name(self.max_name_len, align);
596
        self.write_plain(format!("test {} ... ", name).as_slice())
M
Marijn Haverbeke 已提交
597
    }
598

A
Alex Crichton 已提交
599
    pub fn write_result(&mut self, result: &TestResult) -> io::IoResult<()> {
A
Alex Crichton 已提交
600
        try!(match *result {
601 602 603
            TrOk => self.write_ok(),
            TrFailed => self.write_failed(),
            TrIgnored => self.write_ignored(),
604
            TrMetrics(ref mm) => {
A
Alex Crichton 已提交
605
                try!(self.write_metric());
606
                self.write_plain(format!(": {}", fmt_metrics(mm)).as_slice())
607
            }
608
            TrBench(ref bs) => {
A
Alex Crichton 已提交
609
                try!(self.write_bench());
610 611
                self.write_plain(format!(": {}",
                                         fmt_bench_samples(bs)).as_slice())
612
            }
A
Alex Crichton 已提交
613 614
        });
        self.write_plain("\n")
615
    }
616

A
Alex Crichton 已提交
617 618
    pub fn write_log(&mut self, test: &TestDesc,
                     result: &TestResult) -> io::IoResult<()> {
619
        match self.log_out {
A
Alex Crichton 已提交
620
            None => Ok(()),
L
Luqman Aden 已提交
621
            Some(ref mut o) => {
D
Derek Guenther 已提交
622
                let s = format!("{} {}\n", match *result {
623 624 625
                        TrOk => "ok".to_string(),
                        TrFailed => "failed".to_string(),
                        TrIgnored => "ignored".to_string(),
L
Luqman Aden 已提交
626 627
                        TrMetrics(ref mm) => fmt_metrics(mm),
                        TrBench(ref bs) => fmt_bench_samples(bs)
628
                    }, test.name.as_slice());
A
Alex Crichton 已提交
629
                o.write(s.as_bytes())
630 631
            }
        }
B
Brian Anderson 已提交
632 633
    }

A
Alex Crichton 已提交
634
    pub fn write_failures(&mut self) -> io::IoResult<()> {
A
Alex Crichton 已提交
635
        try!(self.write_plain("\nfailures:\n"));
636
        let mut failures = Vec::new();
637
        let mut fail_out = String::new();
638
        for &(ref f, ref stdout) in self.failures.iter() {
639
            failures.push(f.name.to_string());
640 641
            if stdout.len() > 0 {
                fail_out.push_str(format!("---- {} stdout ----\n\t",
642
                                          f.name.as_slice()).as_slice());
643
                let output = String::from_utf8_lossy(stdout.as_slice());
644
                fail_out.push_str(output.as_slice());
645 646 647 648
                fail_out.push_str("\n");
            }
        }
        if fail_out.len() > 0 {
A
Alex Crichton 已提交
649
            try!(self.write_plain("\n"));
650
            try!(self.write_plain(fail_out.as_slice()));
651
        }
652

A
Alex Crichton 已提交
653
        try!(self.write_plain("\nfailures:\n"));
654
        failures.as_mut_slice().sort();
D
Daniel Micay 已提交
655
        for name in failures.iter() {
656 657
            try!(self.write_plain(format!("    {}\n",
                                          name.as_slice()).as_slice()));
658
        }
A
Alex Crichton 已提交
659
        Ok(())
660 661
    }

A
Alex Crichton 已提交
662
    pub fn write_metric_diff(&mut self, diff: &MetricDiff) -> io::IoResult<()> {
663 664 665 666 667
        let mut noise = 0u;
        let mut improved = 0u;
        let mut regressed = 0u;
        let mut added = 0u;
        let mut removed = 0u;
668

D
Daniel Micay 已提交
669
        for (k, v) in diff.iter() {
670 671 672 673
            match *v {
                LikelyNoise => noise += 1,
                MetricAdded => {
                    added += 1;
A
Alex Crichton 已提交
674
                    try!(self.write_added());
675
                    try!(self.write_plain(format!(": {}\n", *k).as_slice()));
676 677 678
                }
                MetricRemoved => {
                    removed += 1;
A
Alex Crichton 已提交
679
                    try!(self.write_removed());
680
                    try!(self.write_plain(format!(": {}\n", *k).as_slice()));
681 682 683
                }
                Improvement(pct) => {
                    improved += 1;
684
                    try!(self.write_plain(format!(": {}", *k).as_slice()));
A
Alex Crichton 已提交
685
                    try!(self.write_improved());
A
Alex Crichton 已提交
686
                    try!(self.write_plain(format!(" by {:.2}%\n",
687
                                                  pct as f64).as_slice()));
688 689 690
                }
                Regression(pct) => {
                    regressed += 1;
691
                    try!(self.write_plain(format!(": {}", *k).as_slice()));
A
Alex Crichton 已提交
692
                    try!(self.write_regressed());
A
Alex Crichton 已提交
693
                    try!(self.write_plain(format!(" by {:.2}%\n",
694
                                                  pct as f64).as_slice()));
695 696 697
                }
            }
        }
A
Alex Crichton 已提交
698
        try!(self.write_plain(format!("result of ratchet: {} metrics added, \
A
Alex Crichton 已提交
699 700 701
                                        {} removed, {} improved, {} regressed, \
                                        {} noise\n",
                                       added, removed, improved, regressed,
702
                                       noise).as_slice()));
703
        if regressed == 0 {
A
Alex Crichton 已提交
704
            try!(self.write_plain("updated ratchet file\n"));
705
        } else {
A
Alex Crichton 已提交
706
            try!(self.write_plain("left ratchet file untouched\n"));
707
        }
A
Alex Crichton 已提交
708
        Ok(())
709 710
    }

L
Luqman Aden 已提交
711
    pub fn write_run_finish(&mut self,
712
                            ratchet_metrics: &Option<Path>,
A
Alex Crichton 已提交
713
                            ratchet_pct: Option<f64>) -> io::IoResult<bool> {
714
        assert!(self.passed + self.failed + self.ignored + self.measured == self.total);
715 716 717 718

        let ratchet_success = match *ratchet_metrics {
            None => true,
            Some(ref pth) => {
B
Brian Anderson 已提交
719
                try!(self.write_plain(format!("\nusing metrics ratchet: {}\n",
720
                                              pth.display()).as_slice()));
721 722 723
                match ratchet_pct {
                    None => (),
                    Some(pct) =>
A
Alex Crichton 已提交
724
                        try!(self.write_plain(format!("with noise-tolerance \
A
Alex Crichton 已提交
725
                                                         forced to: {}%\n",
726
                                                        pct).as_slice()))
727 728
                }
                let (diff, ok) = self.metrics.ratchet(pth, ratchet_pct);
A
Alex Crichton 已提交
729
                try!(self.write_metric_diff(&diff));
730 731 732 733 734 735
                ok
            }
        };

        let test_success = self.failed == 0u;
        if !test_success {
A
Alex Crichton 已提交
736
            try!(self.write_failures());
737
        }
738

739 740
        let success = ratchet_success && test_success;

A
Alex Crichton 已提交
741
        try!(self.write_plain("\ntest result: "));
742 743
        if success {
            // There's no parallelism at this point so it's safe to use color
A
Alex Crichton 已提交
744
            try!(self.write_ok());
745
        } else {
A
Alex Crichton 已提交
746
            try!(self.write_failed());
747
        }
L
Luqman Aden 已提交
748 749
        let s = format!(". {} passed; {} failed; {} ignored; {} measured\n\n",
                        self.passed, self.failed, self.ignored, self.measured);
750
        try!(self.write_plain(s.as_slice()));
A
Alex Crichton 已提交
751
        return Ok(success);
752
    }
753 754
}

755
pub fn fmt_metrics(mm: &MetricMap) -> String {
756
    let MetricMap(ref mm) = *mm;
757
    let v : Vec<String> = mm.iter()
A
Alex Crichton 已提交
758 759
        .map(|(k,v)| format!("{}: {} (+/- {})", *k,
                             v.value as f64, v.noise as f64))
760
        .collect();
761
    v.connect(", ")
762 763
}

764
pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
765
    if bs.mb_s != 0 {
A
Alex Crichton 已提交
766
        format!("{:>9} ns/iter (+/- {}) = {} MB/s",
767 768 769 770
             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 已提交
771
        format!("{:>9} ns/iter (+/- {})",
772 773
             bs.ns_iter_summ.median as uint,
             (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint)
774
    }
775 776 777
}

// A simple console test runner
778 779 780
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<()> {
781
        match (*event).clone() {
782
            TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
783
            TeWait(ref test, padding) => st.write_test_start(test, padding),
784
            TeResult(test, result, stdout) => {
A
Alex Crichton 已提交
785 786
                try!(st.write_log(&test, &result));
                try!(st.write_result(&result));
787 788 789
                match result {
                    TrOk => st.passed += 1,
                    TrIgnored => st.ignored += 1,
790
                    TrMetrics(mm) => {
791
                        let tname = test.name.as_slice();
792
                        let MetricMap(mm) = mm;
D
Daniel Micay 已提交
793
                        for (k,v) in mm.iter() {
794
                            st.metrics
795 796 797
                              .insert_metric(format!("{}.{}",
                                                     tname,
                                                     k).as_slice(),
798 799
                                             v.value,
                                             v.noise);
800 801 802
                        }
                        st.measured += 1
                    }
803
                    TrBench(bs) => {
804
                        st.metrics.insert_metric(test.name.as_slice(),
805 806
                                                 bs.ns_iter_summ.median,
                                                 bs.ns_iter_summ.max - bs.ns_iter_summ.min);
807
                        st.measured += 1
808
                    }
809 810
                    TrFailed => {
                        st.failed += 1;
811
                        st.failures.push((test, stdout));
812 813
                    }
                }
A
Alex Crichton 已提交
814
                Ok(())
815 816
            }
        }
817
    }
818

A
Alex Crichton 已提交
819
    let mut st = try!(ConsoleTestState::new(opts, None::<StdWriter>));
820 821 822
    fn len_if_padded(t: &TestDescAndFn) -> uint {
        match t.testfn.padding() {
            PadNone => 0u,
823
            PadOnLeft | PadOnRight => t.desc.name.as_slice().len(),
824 825 826 827
        }
    }
    match tests.iter().max_by(|t|len_if_padded(*t)) {
        Some(t) => {
828
            let n = t.desc.name.as_slice();
829 830
            st.max_name_len = n.len();
        },
831 832
        None => {}
    }
A
Alex Crichton 已提交
833
    try!(run_tests(opts, tests, |x| callback(&x, &mut st)));
834 835 836
    match opts.save_metrics {
        None => (),
        Some(ref pth) => {
A
Alex Crichton 已提交
837 838
            try!(st.metrics.save(pth));
            try!(st.write_plain(format!("\nmetrics saved to: {}",
839
                                          pth.display()).as_slice()));
840 841
        }
    }
842
    return st.write_run_finish(&opts.ratchet_metrics, opts.ratchet_noise_percent);
843 844 845 846
}

#[test]
fn should_sort_failures_before_printing_them() {
A
Alex Crichton 已提交
847 848 849 850 851
    let test_a = TestDesc {
        name: StaticTestName("a"),
        ignore: false,
        should_fail: false
    };
852

A
Alex Crichton 已提交
853 854 855 856 857
    let test_b = TestDesc {
        name: StaticTestName("b"),
        ignore: false,
        should_fail: false
    };
858

L
Luqman Aden 已提交
859
    let mut st = ConsoleTestState {
A
Alex Crichton 已提交
860
        log_out: None,
D
Daniel Micay 已提交
861
        out: Raw(Vec::new()),
A
Alex Crichton 已提交
862 863 864 865 866 867 868 869
        use_color: false,
        total: 0u,
        passed: 0u,
        failed: 0u,
        ignored: 0u,
        measured: 0u,
        max_name_len: 10u,
        metrics: MetricMap::new(),
870
        failures: vec!((test_b, Vec::new()), (test_a, Vec::new()))
871
    };
872

873
    st.write_failures().unwrap();
L
Luqman Aden 已提交
874
    let s = match st.out {
D
Daniel Micay 已提交
875
        Raw(ref m) => String::from_utf8_lossy(m[]),
A
Alex Crichton 已提交
876
        Pretty(_) => unreachable!()
L
Luqman Aden 已提交
877
    };
A
Alex Crichton 已提交
878

879 880
    let apos = s.as_slice().find_str("a").unwrap();
    let bpos = s.as_slice().find_str("b").unwrap();
P
Patrick Walton 已提交
881
    assert!(apos < bpos);
882 883
}

884 885 886 887 888 889
fn use_color(opts: &TestOpts) -> bool {
    match opts.color {
        AutoColor => get_concurrency() == 1 && io::stdout().get_ref().isatty(),
        AlwaysColor => true,
        NeverColor => false,
    }
890
}
891

892
#[deriving(Clone)]
B
Brian Anderson 已提交
893
enum TestEvent {
894
    TeFiltered(Vec<TestDesc> ),
895
    TeWait(TestDesc, NamePadding),
896
    TeResult(TestDesc, TestResult, Vec<u8> ),
B
Brian Anderson 已提交
897 898
}

899
pub type MonitorMsg = (TestDesc, TestResult, Vec<u8> );
900

901
fn run_tests(opts: &TestOpts,
902
             tests: Vec<TestDescAndFn> ,
A
Alex Crichton 已提交
903
             callback: |e: TestEvent| -> io::IoResult<()>) -> io::IoResult<()> {
T
Tim Chevalier 已提交
904
    let filtered_tests = filter_tests(opts, tests);
905 906 907
    let filtered_descs = filtered_tests.iter()
                                       .map(|t| t.desc.clone())
                                       .collect();
T
Tim Chevalier 已提交
908

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

911
    let (filtered_tests, filtered_benchs_and_metrics) =
912 913 914 915 916 917
        filtered_tests.partition(|e| {
            match e.testfn {
                StaticTestFn(_) | DynTestFn(_) => true,
                _ => false
            }
        });
918

B
Brian Anderson 已提交
919 920
    // 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 已提交
921
    let concurrency = get_concurrency();
922

923
    let mut remaining = filtered_tests;
924
    remaining.reverse();
925
    let mut pending = 0;
B
Brian Anderson 已提交
926

927
    let (tx, rx) = channel::<MonitorMsg>();
928

929 930
    while pending > 0 || !remaining.is_empty() {
        while pending < concurrency && !remaining.is_empty() {
931
            let test = remaining.pop().unwrap();
932
            if concurrency == 1 {
933 934 935
                // 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 已提交
936
                try!(callback(TeWait(test.desc.clone(), test.testfn.padding())));
937
            }
938
            run_test(opts, !opts.run_tests, test, tx.clone());
939
            pending += 1;
B
Brian Anderson 已提交
940 941
        }

942
        let (desc, result, stdout) = rx.recv();
943
        if concurrency != 1 {
A
Alex Crichton 已提交
944
            try!(callback(TeWait(desc.clone(), PadNone)));
945
        }
A
Alex Crichton 已提交
946
        try!(callback(TeResult(desc, result, stdout)));
947
        pending -= 1;
B
Brian Anderson 已提交
948
    }
949 950

    // All benchmarks run at the end, in serial.
951
    // (this includes metric fns)
A
Aaron Turon 已提交
952
    for b in filtered_benchs_and_metrics.into_iter() {
A
Alex Crichton 已提交
953
        try!(callback(TeWait(b.desc.clone(), b.testfn.padding())));
954
        run_test(opts, !opts.run_benchmarks, b, tx.clone());
955
        let (test, result, stdout) = rx.recv();
A
Alex Crichton 已提交
956
        try!(callback(TeResult(test, result, stdout)));
957
    }
A
Alex Crichton 已提交
958
    Ok(())
B
Brian Anderson 已提交
959 960
}

961
fn get_concurrency() -> uint {
962
    use std::rt;
963 964
    match os::getenv("RUST_TEST_TASKS") {
        Some(s) => {
965
            let opt_n: Option<uint> = FromStr::from_str(s.as_slice());
966 967
            match opt_n {
                Some(n) if n > 0 => n,
S
Steve Klabnik 已提交
968
                _ => panic!("RUST_TEST_TASKS is `{}`, should be a positive integer.", s)
969 970 971
            }
        }
        None => {
972
            rt::default_sched_threads()
973 974
        }
    }
975
}
976

977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
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,
1013
        Some((a,b)) => {
A
Aaron Turon 已提交
1014
            filtered.into_iter().enumerate()
1015 1016 1017
            // 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))
1018
            .map(|(_,t)| t)
1019 1020
            .collect()
        }
1021
    }
1022
}
1023

1024 1025
pub fn run_test(opts: &TestOpts,
                force_ignore: bool,
1026
                test: TestDescAndFn,
1027
                monitor_ch: Sender<MonitorMsg>) {
1028

1029 1030
    let TestDescAndFn {desc, testfn} = test;

1031
    if force_ignore || desc.ignore {
1032
        monitor_ch.send((desc, TrIgnored, Vec::new()));
B
Brian Anderson 已提交
1033
        return;
1034 1035
    }

1036
    fn run_test_inner(desc: TestDesc,
1037
                      monitor_ch: Sender<MonitorMsg>,
1038
                      nocapture: bool,
A
Alex Crichton 已提交
1039
                      testfn: proc():Send) {
1040
        spawn(proc() {
1041 1042 1043 1044
            let (tx, rx) = channel();
            let mut reader = ChanReader::new(rx);
            let stdout = ChanWriter::new(tx.clone());
            let stderr = ChanWriter::new(tx);
1045
            let mut task = TaskBuilder::new().named(match desc.name {
R
Richo Healey 已提交
1046 1047
                DynTestName(ref name) => name.clone().to_string(),
                StaticTestName(name) => name.to_string(),
1048
            });
1049 1050 1051
            if nocapture {
                drop((stdout, stderr));
            } else {
A
Aaron Turon 已提交
1052 1053
                task = task.stdout(box stdout as Box<Writer + Send>);
                task = task.stderr(box stderr as Box<Writer + Send>);
1054
            }
A
Aaron Turon 已提交
1055
            let result_future = task.try_future(testfn);
1056

A
Aaron Turon 已提交
1057
            let stdout = reader.read_to_end().unwrap().into_iter().collect();
1058
            let task_result = result_future.into_inner();
1059
            let test_result = calc_result(&desc, task_result.is_ok());
1060 1061
            monitor_ch.send((desc.clone(), test_result, stdout));
        })
1062 1063 1064
    }

    match testfn {
1065
        DynBenchFn(bencher) => {
L
Liigo Zhuang 已提交
1066
            let bs = ::bench::benchmark(|harness| bencher.run(harness));
1067
            monitor_ch.send((desc, TrBench(bs), Vec::new()));
1068 1069 1070
            return;
        }
        StaticBenchFn(benchfn) => {
L
Liigo Zhuang 已提交
1071
            let bs = ::bench::benchmark(|harness| benchfn(harness));
1072
            monitor_ch.send((desc, TrBench(bs), Vec::new()));
1073 1074
            return;
        }
1075 1076 1077
        DynMetricFn(f) => {
            let mut mm = MetricMap::new();
            f(&mut mm);
1078
            monitor_ch.send((desc, TrMetrics(mm), Vec::new()));
1079 1080 1081 1082 1083
            return;
        }
        StaticMetricFn(f) => {
            let mut mm = MetricMap::new();
            f(&mut mm);
1084
            monitor_ch.send((desc, TrMetrics(mm), Vec::new()));
1085 1086
            return;
        }
1087 1088 1089
        DynTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture, f),
        StaticTestFn(f) => run_test_inner(desc, monitor_ch, opts.nocapture,
                                          proc() f())
1090
    }
1091 1092
}

1093
fn calc_result(desc: &TestDesc, task_succeeded: bool) -> TestResult {
1094
    if task_succeeded {
1095
        if desc.should_fail { TrFailed }
B
Brian Anderson 已提交
1096
        else { TrOk }
1097
    } else {
1098
        if desc.should_fail { TrOk }
B
Brian Anderson 已提交
1099
        else { TrFailed }
1100
    }
1101 1102
}

1103 1104 1105

impl ToJson for Metric {
    fn to_json(&self) -> json::Json {
A
Adolfo Ochagavía 已提交
1106
        let mut map = TreeMap::new();
A
Alex Crichton 已提交
1107 1108 1109
        map.insert("value".to_string(), json::Json::F64(self.value));
        map.insert("noise".to_string(), json::Json::F64(self.noise));
        json::Json::Object(map)
1110 1111 1112
    }
}

S
Sean McArthur 已提交
1113

1114 1115
impl MetricMap {

1116
    pub fn new() -> MetricMap {
1117 1118 1119 1120
        MetricMap(TreeMap::new())
    }

    /// Load MetricDiff from a file.
A
Alex Crichton 已提交
1121
    ///
1122
    /// # Panics
A
Alex Crichton 已提交
1123
    ///
S
Steve Klabnik 已提交
1124
    /// This function will panic if the path does not exist or the path does not
A
Alex Crichton 已提交
1125
    /// contain a valid metric map.
1126
    pub fn load(p: &Path) -> MetricMap {
1127
        assert!(p.exists());
A
Alex Crichton 已提交
1128
        let mut f = File::open(p).unwrap();
1129
        let value = json::from_reader(&mut f as &mut io::Reader).unwrap();
F
Flavio Percoco 已提交
1130 1131 1132
        let mut decoder = json::Decoder::new(value);
        MetricMap(match Decodable::decode(&mut decoder) {
            Ok(t) => t,
S
Steve Klabnik 已提交
1133
            Err(e) => panic!("failure decoding JSON: {}", e)
F
Flavio Percoco 已提交
1134
        })
1135 1136 1137
    }

    /// Write MetricDiff to a file.
A
Alex Crichton 已提交
1138
    pub fn save(&self, p: &Path) -> io::IoResult<()> {
A
Alex Crichton 已提交
1139
        let mut file = try!(File::create(p));
1140
        let MetricMap(ref map) = *self;
1141 1142 1143 1144

        // FIXME(pcwalton): Yuck.
        let mut new_map = TreeMap::new();
        for (ref key, ref value) in map.iter() {
1145
            new_map.insert(key.to_string(), (*value).clone());
1146 1147 1148
        }

        new_map.to_json().to_pretty_writer(&mut file)
1149 1150
    }

1151 1152 1153 1154 1155 1156 1157
    /// 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,
1158
                          noise_pct: Option<f64>) -> MetricDiff {
1159
        let mut diff : MetricDiff = TreeMap::new();
1160 1161
        let MetricMap(ref selfmap) = *self;
        let MetricMap(ref old) = *old;
D
Daniel Micay 已提交
1162
        for (k, vold) in old.iter() {
1163
            let r = match selfmap.get(k) {
1164 1165
                None => MetricRemoved,
                Some(v) => {
1166
                    let delta = v.value - vold.value;
1167
                    let noise = match noise_pct {
1168
                        None => vold.noise.abs().max(v.noise.abs()),
1169 1170
                        Some(pct) => vold.value * pct / 100.0
                    };
1171
                    if delta.abs() <= noise {
1172 1173
                        LikelyNoise
                    } else {
1174
                        let pct = delta.abs() / vold.value.max(f64::EPSILON) * 100.0;
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196
                        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)
                            }
                        }
                    }
                }
            };
1197
            diff.insert((*k).clone(), r);
1198
        }
1199 1200
        let MetricMap(ref map) = *self;
        for (k, _) in map.iter() {
1201
            if !diff.contains_key(k) {
1202
                diff.insert((*k).clone(), MetricAdded);
1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
            }
        }
        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
        };
1226
        let MetricMap(ref mut map) = *self;
1227
        map.insert(name.to_string(), m);
1228 1229 1230 1231 1232 1233 1234 1235
    }

    /// 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.
1236
    pub fn ratchet(&self, p: &Path, pct: Option<f64>) -> (MetricDiff, bool) {
1237
        let old = if p.exists() {
1238 1239 1240 1241 1242
            MetricMap::load(p)
        } else {
            MetricMap::new()
        };

1243
        let diff : MetricDiff = self.compare_to_old(&old, pct);
1244
        let ok = diff.iter().all(|(_, v)| {
1245 1246 1247 1248
            match *v {
                Regression(_) => false,
                _ => true
            }
1249
        });
1250 1251

        if ok {
A
Alex Crichton 已提交
1252
            self.save(p).unwrap();
1253 1254 1255 1256 1257 1258 1259 1260
        }
        return (diff, ok)
    }
}


// Benchmarking

H
Huon Wilson 已提交
1261
/// A function that is opaque to the optimizer, to allow benchmarks to
1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
/// 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))}
}


1273
impl Bencher {
1274
    /// Callback for benchmark functions to run in their body.
1275
    pub fn iter<T>(&mut self, inner: || -> T) {
1276 1277 1278 1279 1280 1281
        self.dur = Duration::span(|| {
            let k = self.iterations;
            for _ in range(0u64, k) {
                black_box(inner());
            }
        });
1282
    }
1283

1284
    pub fn ns_elapsed(&mut self) -> u64 {
1285
        self.dur.num_nanoseconds().unwrap() as u64
1286
    }
1287

1288 1289 1290 1291
    pub fn ns_per_iter(&mut self) -> u64 {
        if self.iterations == 0 {
            0
        } else {
M
Michael Darakananda 已提交
1292
            self.ns_elapsed() / cmp::max(self.iterations, 1)
1293
        }
1294
    }
1295

1296
    pub fn bench_n(&mut self, n: u64, f: |&mut Bencher|) {
1297 1298 1299
        self.iterations = n;
        f(self);
    }
1300

1301
    // This is a more statistics-driven benchmark algorithm
1302
    pub fn auto_bench(&mut self, f: |&mut Bencher|) -> stats::Summary<f64> {
1303

1304 1305
        // Initial bench run to get ballpark figure.
        let mut n = 1_u64;
1306
        self.bench_n(n, |x| f(x));
1307

1308 1309 1310 1311 1312
        // 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 已提交
1313
            n = 1_000_000 / cmp::max(self.ns_per_iter(), 1);
1314
        }
1315 1316 1317 1318 1319 1320 1321
        // 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; }

1322
        let mut total_run = Duration::nanoseconds(0);
N
Nick Cameron 已提交
1323
        let samples : &mut [f64] = &mut [0.0_f64, ..50];
1324
        loop {
1325 1326
            let mut summ = None;
            let mut summ5 = None;
1327

1328
            let loop_run = Duration::span(|| {
1329

1330 1331 1332 1333
                for p in samples.iter_mut() {
                    self.bench_n(n, |x| f(x));
                    *p = self.ns_per_iter() as f64;
                };
1334

1335 1336
                stats::winsorize(samples, 5.0);
                summ = Some(stats::Summary::new(samples));
1337

1338 1339 1340 1341
                for p in samples.iter_mut() {
                    self.bench_n(5 * n, |x| f(x));
                    *p = self.ns_per_iter() as f64;
                };
1342

1343 1344 1345 1346 1347
                stats::winsorize(samples, 5.0);
                summ5 = Some(stats::Summary::new(samples));
            });
            let summ = summ.unwrap();
            let summ5 = summ5.unwrap();
1348

1349
            // If we've run for 100ms and seem to have converged to a
1350
            // stable median.
1351
            if loop_run.num_milliseconds() > 100 &&
1352 1353 1354
                summ.median_abs_dev_pct < 1.0 &&
                summ.median - summ5.median < summ5.median_abs_dev {
                return summ5;
1355 1356
            }

1357
            total_run = total_run + loop_run;
1358
            // Longest we ever run for is 3s.
1359
            if total_run.num_seconds() > 3 {
1360
                return summ5;
1361
            }
1362

1363
            n *= 2;
1364 1365
        }
    }
1366 1367 1368
}

pub mod bench {
M
Michael Darakananda 已提交
1369
    use std::cmp;
1370
    use std::time::Duration;
1371
    use super::{Bencher, BenchSamples};
1372

1373 1374
    pub fn benchmark(f: |&mut Bencher|) -> BenchSamples {
        let mut bs = Bencher {
1375
            iterations: 0,
1376
            dur: Duration::nanoseconds(0),
1377 1378 1379
            bytes: 0
        };

1380
        let ns_iter_summ = bs.auto_bench(f);
1381

M
Michael Darakananda 已提交
1382
        let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
1383
        let iter_s = 1_000_000_000 / ns_iter;
1384 1385 1386
        let mb_s = (bs.bytes * iter_s) / 1_000_000;

        BenchSamples {
1387
            ns_iter_summ: ns_iter_summ,
1388 1389 1390 1391 1392
            mb_s: mb_s as uint
        }
    }
}

1393 1394
#[cfg(test)]
mod tests {
1395
    use test::{TrFailed, TrIgnored, TrOk, filter_tests, parse_opts,
L
Liigo Zhuang 已提交
1396
               TestDesc, TestDescAndFn, TestOpts, run_test,
1397 1398
               Metric, MetricMap, MetricAdded, MetricRemoved,
               Improvement, Regression, LikelyNoise,
1399
               StaticTestName, DynTestName, DynTestFn};
1400
    use std::io::TempDir;
1401

1402
    #[test]
1403
    pub fn do_not_run_ignored_tests() {
S
Steve Klabnik 已提交
1404
        fn f() { panic!(); }
1405 1406
        let desc = TestDescAndFn {
            desc: TestDesc {
1407
                name: StaticTestName("whatever"),
1408 1409 1410
                ignore: true,
                should_fail: false
            },
1411
            testfn: DynTestFn(proc() f()),
1412
        };
1413
        let (tx, rx) = channel();
1414
        run_test(&TestOpts::new(), false, desc, tx);
1415
        let (_, res, _) = rx.recv();
P
Patrick Walton 已提交
1416
        assert!(res != TrOk);
1417 1418 1419
    }

    #[test]
1420
    pub fn ignored_tests_result_in_ignored() {
1421
        fn f() { }
1422 1423
        let desc = TestDescAndFn {
            desc: TestDesc {
1424
                name: StaticTestName("whatever"),
1425 1426 1427
                ignore: true,
                should_fail: false
            },
1428
            testfn: DynTestFn(proc() f()),
1429
        };
1430
        let (tx, rx) = channel();
1431
        run_test(&TestOpts::new(), false, desc, tx);
1432
        let (_, res, _) = rx.recv();
1433
        assert!(res == TrIgnored);
1434 1435 1436
    }

    #[test]
1437
    fn test_should_fail() {
S
Steve Klabnik 已提交
1438
        fn f() { panic!(); }
1439 1440
        let desc = TestDescAndFn {
            desc: TestDesc {
1441
                name: StaticTestName("whatever"),
1442 1443 1444
                ignore: false,
                should_fail: true
            },
1445
            testfn: DynTestFn(proc() f()),
1446
        };
1447
        let (tx, rx) = channel();
1448
        run_test(&TestOpts::new(), false, desc, tx);
1449
        let (_, res, _) = rx.recv();
1450
        assert!(res == TrOk);
1451 1452 1453
    }

    #[test]
1454
    fn test_should_fail_but_succeeds() {
1455
        fn f() { }
1456 1457
        let desc = TestDescAndFn {
            desc: TestDesc {
1458
                name: StaticTestName("whatever"),
1459 1460 1461
                ignore: false,
                should_fail: true
            },
1462
            testfn: DynTestFn(proc() f()),
1463
        };
1464
        let (tx, rx) = channel();
1465
        run_test(&TestOpts::new(), false, desc, tx);
1466
        let (_, res, _) = rx.recv();
1467
        assert!(res == TrFailed);
1468 1469 1470
    }

    #[test]
1471
    fn first_free_arg_should_be_a_filter() {
1472
        let args = vec!("progname".to_string(), "some_regex_filter".to_string());
1473
        let opts = match parse_opts(args.as_slice()) {
B
Brian Anderson 已提交
1474
            Some(Ok(o)) => o,
S
Steve Klabnik 已提交
1475
            _ => panic!("Malformed arg in first_free_arg_should_be_a_filter")
B
Brian Anderson 已提交
1476
        };
1477
        assert!(opts.filter.expect("should've found filter").is_match("some_regex_filter"))
1478 1479 1480
    }

    #[test]
1481
    fn parse_ignored_flag() {
1482 1483 1484
        let args = vec!("progname".to_string(),
                        "filter".to_string(),
                        "--ignored".to_string());
1485
        let opts = match parse_opts(args.as_slice()) {
B
Brian Anderson 已提交
1486
            Some(Ok(o)) => o,
S
Steve Klabnik 已提交
1487
            _ => panic!("Malformed arg in parse_ignored_flag")
B
Brian Anderson 已提交
1488
        };
P
Patrick Walton 已提交
1489
        assert!((opts.run_ignored));
1490 1491 1492
    }

    #[test]
1493
    pub fn filter_for_ignored_option() {
1494 1495 1496
        // 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

1497 1498 1499
        let mut opts = TestOpts::new();
        opts.run_tests = true;
        opts.run_ignored = true;
1500

1501
        let tests = vec!(
1502 1503
            TestDescAndFn {
                desc: TestDesc {
1504
                    name: StaticTestName("1"),
1505 1506 1507
                    ignore: true,
                    should_fail: false,
                },
1508
                testfn: DynTestFn(proc() {}),
1509
            },
1510 1511
            TestDescAndFn {
                desc: TestDesc {
1512
                    name: StaticTestName("2"),
1513 1514 1515
                    ignore: false,
                    should_fail: false
                },
1516
                testfn: DynTestFn(proc() {}),
1517
            });
B
Brian Anderson 已提交
1518
        let filtered = filter_tests(&opts, tests);
1519

1520
        assert_eq!(filtered.len(), 1);
1521
        assert_eq!(filtered[0].desc.name.to_string(),
1522
                   "1".to_string());
1523
        assert!(filtered[0].desc.ignore == false);
1524 1525 1526
    }

    #[test]
1527
    pub fn sort_tests() {
1528 1529
        let mut opts = TestOpts::new();
        opts.run_tests = true;
1530 1531

        let names =
1532 1533 1534 1535 1536 1537 1538 1539 1540
            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());
1541 1542
        let tests =
        {
1543
            fn testfn() { }
1544
            let mut tests = Vec::new();
D
Daniel Micay 已提交
1545
            for name in names.iter() {
1546 1547
                let test = TestDescAndFn {
                    desc: TestDesc {
1548
                        name: DynTestName((*name).clone()),
1549
                        ignore: false,
1550 1551
                        should_fail: false
                    },
P
Patrick Walton 已提交
1552
                    testfn: DynTestFn(testfn),
1553
                };
L
Luqman Aden 已提交
1554
                tests.push(test);
1555
            }
L
Luqman Aden 已提交
1556
            tests
1557
        };
B
Brian Anderson 已提交
1558
        let filtered = filter_tests(&opts, tests);
1559

1560
        let expected =
1561 1562 1563 1564 1565 1566 1567 1568 1569
            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());
1570

1571
        for (a, b) in expected.iter().zip(filtered.iter()) {
1572
            assert!(*a == b.desc.name.to_string());
1573 1574
        }
    }
1575

1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588
    #[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 {
1589
                    name: DynTestName(name.to_string()),
1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606
                    ignore: false,
                    should_fail: false
                },
                testfn: DynTestFn(test_fn)
            }
        }).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);
        }
    }

1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630
    #[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);

1631 1632 1633 1634
        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()),
1635
                   Regression(100.0));
1636
        assert_eq!(*(diff1.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1637
                   Improvement(50.0));
1638
        assert_eq!(*(diff1.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1639
                   Regression(50.0));
1640
        assert_eq!(*(diff1.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1641
                   Improvement(100.0));
1642 1643 1644 1645
        assert_eq!(diff1.len(), 7);

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

1646 1647 1648 1649
        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()),
1650
                   LikelyNoise);
1651
        assert_eq!(*(diff2.get(&"in-both-want-downwards-and-improved".to_string()).unwrap()),
1652
                   LikelyNoise);
1653
        assert_eq!(*(diff2.get(&"in-both-want-upwards-but-regressed".to_string()).unwrap()),
1654
                   LikelyNoise);
1655
        assert_eq!(*(diff2.get(&"in-both-want-upwards-and-improved".to_string()).unwrap()),
1656
                   LikelyNoise);
1657 1658 1659
        assert_eq!(diff2.len(), 7);
    }

1660
    #[test]
1661 1662
    pub fn ratchet_test() {

1663
        let dpth = TempDir::new("test-ratchet").ok().expect("missing test for ratchet");
1664
        let pth = dpth.path().join("ratchet.json");
1665 1666 1667 1668 1669 1670 1671 1672 1673

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

1674
        m1.save(&pth).unwrap();
1675 1676 1677 1678 1679

        // 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);
1680 1681
        assert_eq!(*(diff1.get(&"runtime".to_string()).unwrap()), Regression(10.0));
        assert_eq!(*(diff1.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1682 1683 1684

        // Check that it was not rewritten.
        let m3 = MetricMap::load(&pth);
1685
        let MetricMap(m3) = m3;
1686
        assert_eq!(m3.len(), 2);
1687 1688
        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));
1689 1690 1691 1692 1693 1694

        // 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);
1695 1696
        assert_eq!(*(diff2.get(&"runtime".to_string()).unwrap()), LikelyNoise);
        assert_eq!(*(diff2.get(&"throughput".to_string()).unwrap()), LikelyNoise);
1697 1698 1699

        // Check that it was rewritten.
        let m4 = MetricMap::load(&pth);
1700
        let MetricMap(m4) = m4;
1701
        assert_eq!(m4.len(), 2);
1702 1703
        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));
1704
    }
1705
}