lib.rs 51.6 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 14 15
// Support code for rustc's built in test runner generator. Currently,
// none 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.

L
Liigo Zhuang 已提交
16 17 18 19 20 21 22 23 24 25
#[crate_id = "test#0.10-pre"];
#[comment = "Rust internal test library only used by rustc"];
#[license = "MIT/ASL2"];
#[crate_type = "rlib"];
#[crate_type = "dylib"];

#[feature(asm)];

extern crate collections;
extern crate extra;
A
Alex Crichton 已提交
26
extern crate getopts;
L
Liigo Zhuang 已提交
27
extern crate serialize;
A
Alex Crichton 已提交
28
extern crate term;
29

30
use collections::TreeMap;
L
Liigo Zhuang 已提交
31 32 33 34 35 36 37 38 39
use extra::json::ToJson;
use extra::json;
use extra::stats::Stats;
use extra::stats;
use extra::time::precise_time_ns;
use getopts::{OptGroup, optflag, optopt};
use serialize::Decodable;
use term::Terminal;
use term::color::{Color, RED, YELLOW, GREEN, CYAN};
40

M
Michael Darakananda 已提交
41
use std::cmp;
A
Alex Crichton 已提交
42
use std::io;
43
use std::io::{File, PortReader, ChanWriter};
L
Luqman Aden 已提交
44
use std::io::stdio::StdWriter;
45
use std::str;
46 47
use std::task;
use std::to_str::ToStr;
48
use std::f64;
49
use std::os;
50

L
Liigo Zhuang 已提交
51 52 53 54 55 56 57 58 59 60 61
// to be used by rustc to compile tests in libtest
pub mod test {
    pub use {BenchHarness, TestName, TestResult, TestDesc,
             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,
             parse_opts};
}

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

67
#[deriving(Clone)]
68
pub enum TestName {
69
    StaticTestName(&'static str),
70 71 72
    DynTestName(~str)
}
impl ToStr for TestName {
73
    fn to_str(&self) -> ~str {
74
        match (*self).clone() {
75 76
            StaticTestName(s) => s.to_str(),
            DynTestName(s) => s.to_str()
77 78 79
        }
    }
}
80

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
#[deriving(Clone)]
enum NamePadding { PadNone, PadOnLeft, PadOnRight }

impl TestDesc {
    fn padded_name(&self, column_count: uint, align: NamePadding) -> ~str {
        use std::num::Saturating;
        let name = self.name.to_str();
        let fill = column_count.saturating_sub(name.len());
        let pad = " ".repeat(fill);
        match align {
            PadNone => name,
            PadOnLeft => pad.append(name),
            PadOnRight => name.append(pad),
        }
    }
}

98 99 100 101 102
/// Represents a benchmark function.
pub trait TDynBenchFn {
    fn run(&self, harness: &mut BenchHarness);
}

103 104 105 106
// A function that runs a test. If the function returns successfully,
// the test succeeds; if the function fails then the test fails. We
// may need to come up with a more clever definition of test in order
// to support isolation of tests into tasks.
107 108 109
pub enum TestFn {
    StaticTestFn(extern fn()),
    StaticBenchFn(extern fn(&mut BenchHarness)),
110 111 112 113
    StaticMetricFn(proc(&mut MetricMap)),
    DynTestFn(proc()),
    DynMetricFn(proc(&mut MetricMap)),
    DynBenchFn(~TDynBenchFn)
114 115
}

116 117 118
impl TestFn {
    fn padding(&self) -> NamePadding {
        match self {
A
Alex Crichton 已提交
119 120 121 122 123 124
            &StaticTestFn(..)   => PadNone,
            &StaticBenchFn(..)  => PadOnRight,
            &StaticMetricFn(..) => PadOnRight,
            &DynTestFn(..)      => PadNone,
            &DynMetricFn(..)    => PadOnRight,
            &DynBenchFn(..)     => PadOnRight,
125 126 127 128
        }
    }
}

129 130
// Structure passed to BenchFns
pub struct BenchHarness {
131 132 133
    priv iterations: u64,
    priv ns_start: u64,
    priv ns_end: u64,
134 135
    bytes: u64
}
136 137 138

// The definition of a single test. A test runner will run a list of
// these.
139
#[deriving(Clone)]
140
pub struct TestDesc {
B
Brian Anderson 已提交
141
    name: TestName,
142 143
    ignore: bool,
    should_fail: bool
144
}
145

146 147 148 149 150
pub struct TestDescAndFn {
    desc: TestDesc,
    testfn: TestFn,
}

151
#[deriving(Clone, Encodable, Decodable, Eq)]
152
pub struct Metric {
153 154
    priv value: f64,
    priv noise: f64
155 156
}

L
Liigo Zhuang 已提交
157 158 159 160 161 162
impl Metric {
    pub fn new(value: f64, noise: f64) -> Metric {
        Metric {value: value, noise: noise}
    }
}

163
#[deriving(Eq)]
164 165
pub struct MetricMap(TreeMap<~str,Metric>);

166
impl Clone for MetricMap {
167
    fn clone(&self) -> MetricMap {
168 169
        let MetricMap(ref map) = *self;
        MetricMap(map.clone())
170 171 172
    }
}

173
/// Analysis of a single change in metric
174
#[deriving(Eq)]
175 176 177 178 179 180 181 182 183 184
pub enum MetricChange {
    LikelyNoise,
    MetricAdded,
    MetricRemoved,
    Improvement(f64),
    Regression(f64)
}

pub type MetricDiff = TreeMap<~str,MetricChange>;

185
// The default console test runner. It accepts the command line
186
// arguments and a vector of test_descs.
187
pub fn test_main(args: &[~str], tests: ~[TestDescAndFn]) {
M
Marijn Haverbeke 已提交
188
    let opts =
189
        match parse_opts(args) {
B
Brian Anderson 已提交
190
            Some(Ok(o)) => o,
191
            Some(Err(msg)) => fail!("{}", msg),
B
Brian Anderson 已提交
192
            None => return
M
Marijn Haverbeke 已提交
193
        };
A
Alex Crichton 已提交
194 195 196 197 198
    match run_tests_console(&opts, tests) {
        Ok(true) => {}
        Ok(false) => fail!("Some tests failed"),
        Err(e) => fail!("io error when running tests: {}", e),
    }
199 200
}

201 202 203 204 205 206 207 208
// A variant optimized for invocation with a static test vector.
// This will fail (intentionally) when fed any dynamic tests, because
// 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 &[].
pub fn test_main_static(args: &[~str], tests: &[TestDescAndFn]) {
209
    let owned_tests = tests.map(|t| {
210 211
        match t.testfn {
            StaticTestFn(f) =>
212
            TestDescAndFn { testfn: StaticTestFn(f), desc: t.desc.clone() },
213 214

            StaticBenchFn(f) =>
215
            TestDescAndFn { testfn: StaticBenchFn(f), desc: t.desc.clone() },
216 217

            _ => {
218
                fail!("non-static tests passed to test::test_main_static");
219 220
            }
        }
221
    });
222 223 224
    test_main(args, owned_tests)
}

225 226 227
pub struct TestOpts {
    filter: Option<~str>,
    run_ignored: bool,
228 229
    run_tests: bool,
    run_benchmarks: bool,
230
    ratchet_metrics: Option<Path>,
231
    ratchet_noise_percent: Option<f64>,
232
    save_metrics: Option<Path>,
233
    test_shard: Option<(uint,uint)>,
234
    logfile: Option<Path>
235
}
236

237 238
/// Result of parsing the options.
pub type OptRes = Result<TestOpts, ~str>;
239

240 241 242 243 244 245
fn optgroups() -> ~[getopts::OptGroup] {
    ~[getopts::optflag("", "ignored", "Run ignored tests"),
      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",
246
                     "PATH"),
247
      getopts::optopt("", "ratchet-metrics",
248 249 250
                     "Location to load and save metrics from. The metrics \
                      loaded are cause benchmarks to fail if they run too \
                      slowly", "PATH"),
251
      getopts::optopt("", "ratchet-noise-percent",
252 253
                     "Tests within N% of the recorded metrics will be \
                      considered as passing", "PERCENTAGE"),
254
      getopts::optopt("", "logfile", "Write logs to the specified file instead \
255
                          of stdout", "PATH"),
256
      getopts::optopt("", "test-shard", "run shard A, of B shards, worth of the testsuite",
257
                     "A.B")]
258 259
}

B
Brian Anderson 已提交
260
fn usage(binary: &str, helpstr: &str) {
A
Alex Crichton 已提交
261
    let message = format!("Usage: {} [OPTIONS] [FILTER]", binary);
262
    println!("{}", getopts::usage(message, optgroups()));
263
    println!("");
264
    if helpstr == "help" {
265
        println!("{}", "\
266 267 268 269
The FILTER is matched against the name of all tests to run, and if any tests
have a substring match, only those tests are run.

By default, all tests are run in parallel. This can be altered with the
270
RUST_TEST_TASKS environment variable when running tests (set it to 1).
271 272 273 274 275 276

Test Attributes:

    #[test]        - Indicates a function is a test to be run. This function
                     takes no arguments.
    #[bench]       - Indicates a function is a benchmark to be run. This
L
Liigo Zhuang 已提交
277
                     function takes one argument (test::BenchHarness).
278
    #[should_fail] - This function (also labeled with #[test]) will only pass if
279
                     the code causes a failure (an assertion failure or fail!)
280 281 282 283 284 285 286 287
    #[ignore]      - When applied to a function which is already attributed as a
                     test, then the test runner will ignore these tests during
                     normal test runs. Running with --ignored will run these
                     tests. This may also be written as #[ignore(cfg(...))] to
                     ignore the test on certain configurations.");
    }
}

288
// Parses command line arguments into test options
B
Brian Anderson 已提交
289
pub fn parse_opts(args: &[~str]) -> Option<OptRes> {
290
    let args_ = args.tail();
291
    let matches =
292
        match getopts::getopts(args_, optgroups()) {
L
Luqman Aden 已提交
293
          Ok(m) => m,
B
Brian Anderson 已提交
294
          Err(f) => return Some(Err(f.to_err_msg()))
M
Marijn Haverbeke 已提交
295 296
        };

B
Brian Anderson 已提交
297 298
    if matches.opt_present("h") { usage(args[0], "h"); return None; }
    if matches.opt_present("help") { usage(args[0], "help"); return None; }
299

M
Marijn Haverbeke 已提交
300
    let filter =
Y
Youngmin Yoo 已提交
301
        if matches.free.len() > 0 {
302 303 304 305
            Some((matches).free[0].clone())
        } else {
            None
        };
306

307
    let run_ignored = matches.opt_present("ignored");
308

309
    let logfile = matches.opt_str("logfile");
310
    let logfile = logfile.map(|s| Path::new(s));
311

312
    let run_benchmarks = matches.opt_present("bench");
313
    let run_tests = ! run_benchmarks ||
314
        matches.opt_present("test");
315

316
    let ratchet_metrics = matches.opt_str("ratchet-metrics");
317
    let ratchet_metrics = ratchet_metrics.map(|s| Path::new(s));
318

319
    let ratchet_noise_percent = matches.opt_str("ratchet-noise-percent");
320
    let ratchet_noise_percent = ratchet_noise_percent.map(|s| from_str::<f64>(s).unwrap());
321

322
    let save_metrics = matches.opt_str("save-metrics");
323
    let save_metrics = save_metrics.map(|s| Path::new(s));
324

325
    let test_shard = matches.opt_str("test-shard");
326 327
    let test_shard = opt_shard(test_shard);

328 329 330
    let test_opts = TestOpts {
        filter: filter,
        run_ignored: run_ignored,
331 332
        run_tests: run_tests,
        run_benchmarks: run_benchmarks,
333
        ratchet_metrics: ratchet_metrics,
334
        ratchet_noise_percent: ratchet_noise_percent,
335
        save_metrics: save_metrics,
336
        test_shard: test_shard,
337
        logfile: logfile
338
    };
339

B
Brian Anderson 已提交
340
    Some(Ok(test_opts))
341 342
}

343 344 345 346
pub fn opt_shard(maybestr: Option<~str>) -> Option<(uint,uint)> {
    match maybestr {
        None => None,
        Some(s) => {
347 348 349 350 351
            let vector = s.split('.').to_owned_vec();
            if vector.len() == 2 {
                match (from_str::<uint>(vector[0]),
                       from_str::<uint>(vector[1])) {
                    (Some(a), Some(b)) => Some((a, b)),
352
                    _ => None
353 354 355
                }
            } else {
                None
356 357 358 359 360 361
            }
        }
    }
}


362
#[deriving(Clone, Eq)]
363
pub struct BenchSamples {
364 365
    priv ns_iter_summ: stats::Summary,
    priv mb_s: uint
366 367
}

368
#[deriving(Clone, Eq)]
369 370 371 372 373
pub enum TestResult {
    TrOk,
    TrFailed,
    TrIgnored,
    TrMetrics(MetricMap),
374
    TrBench(BenchSamples),
375
}
376

A
Alex Crichton 已提交
377 378 379 380 381
enum OutputLocation<T> {
    Pretty(term::Terminal<T>),
    Raw(T),
}

L
Luqman Aden 已提交
382 383
struct ConsoleTestState<T> {
    log_out: Option<File>,
A
Alex Crichton 已提交
384
    out: OutputLocation<T>,
385
    use_color: bool,
386 387 388 389
    total: uint,
    passed: uint,
    failed: uint,
    ignored: uint,
390
    measured: uint,
391
    metrics: MetricMap,
392
    failures: ~[(TestDesc, ~[u8])],
393
    max_name_len: uint, // number of columns to fill when aligning names
394
}
395

L
Luqman Aden 已提交
396
impl<T: Writer> ConsoleTestState<T> {
A
Alex Crichton 已提交
397 398
    pub fn new(opts: &TestOpts,
               _: Option<T>) -> io::IoResult<ConsoleTestState<StdWriter>> {
399
        let log_out = match opts.logfile {
A
Alex Crichton 已提交
400
            Some(ref path) => Some(try!(File::create(path))),
401 402
            None => None
        };
L
Luqman Aden 已提交
403
        let out = match term::Terminal::new(io::stdout()) {
A
Alex Crichton 已提交
404 405
            Err(_) => Raw(io::stdout()),
            Ok(t) => Pretty(t)
406
        };
A
Alex Crichton 已提交
407
        Ok(ConsoleTestState {
408 409 410 411 412 413 414
            out: out,
            log_out: log_out,
            use_color: use_color(),
            total: 0u,
            passed: 0u,
            failed: 0u,
            ignored: 0u,
415
            measured: 0u,
416
            metrics: MetricMap::new(),
417 418
            failures: ~[],
            max_name_len: 0u,
A
Alex Crichton 已提交
419
        })
420 421
    }

A
Alex Crichton 已提交
422 423
    pub fn write_ok(&mut self) -> io::IoResult<()> {
        self.write_pretty("ok", term::color::GREEN)
424
    }
425

A
Alex Crichton 已提交
426 427
    pub fn write_failed(&mut self) -> io::IoResult<()> {
        self.write_pretty("FAILED", term::color::RED)
428 429
    }

A
Alex Crichton 已提交
430 431
    pub fn write_ignored(&mut self) -> io::IoResult<()> {
        self.write_pretty("ignored", term::color::YELLOW)
432
    }
433

A
Alex Crichton 已提交
434 435
    pub fn write_metric(&mut self) -> io::IoResult<()> {
        self.write_pretty("metric", term::color::CYAN)
436 437
    }

A
Alex Crichton 已提交
438 439
    pub fn write_bench(&mut self) -> io::IoResult<()> {
        self.write_pretty("bench", term::color::CYAN)
440
    }
441

A
Alex Crichton 已提交
442 443
    pub fn write_added(&mut self) -> io::IoResult<()> {
        self.write_pretty("added", term::color::GREEN)
444 445
    }

A
Alex Crichton 已提交
446 447
    pub fn write_improved(&mut self) -> io::IoResult<()> {
        self.write_pretty("improved", term::color::GREEN)
448 449
    }

A
Alex Crichton 已提交
450 451
    pub fn write_removed(&mut self) -> io::IoResult<()> {
        self.write_pretty("removed", term::color::YELLOW)
452 453
    }

A
Alex Crichton 已提交
454 455
    pub fn write_regressed(&mut self) -> io::IoResult<()> {
        self.write_pretty("regressed", term::color::RED)
456 457
    }

L
Luqman Aden 已提交
458
    pub fn write_pretty(&mut self,
459
                        word: &str,
A
Alex Crichton 已提交
460
                        color: term::color::Color) -> io::IoResult<()> {
L
Luqman Aden 已提交
461
        match self.out {
A
Alex Crichton 已提交
462
            Pretty(ref mut term) => {
463
                if self.use_color {
A
Alex Crichton 已提交
464
                    try!(term.fg(color));
465
                }
A
Alex Crichton 已提交
466
                try!(term.write(word.as_bytes()));
467
                if self.use_color {
A
Alex Crichton 已提交
468
                    try!(term.reset());
469
                }
A
Alex Crichton 已提交
470
                Ok(())
471
            }
A
Alex Crichton 已提交
472
            Raw(ref mut stdout) => stdout.write(word.as_bytes())
L
Luqman Aden 已提交
473 474 475
        }
    }

A
Alex Crichton 已提交
476
    pub fn write_plain(&mut self, s: &str) -> io::IoResult<()> {
L
Luqman Aden 已提交
477
        match self.out {
A
Alex Crichton 已提交
478 479
            Pretty(ref mut term) => term.write(s.as_bytes()),
            Raw(ref mut stdout) => stdout.write(s.as_bytes())
480 481 482
        }
    }

A
Alex Crichton 已提交
483
    pub fn write_run_start(&mut self, len: uint) -> io::IoResult<()> {
484 485
        self.total = len;
        let noun = if len != 1 { &"tests" } else { &"test" };
A
Alex Crichton 已提交
486
        self.write_plain(format!("\nrunning {} {}\n", len, noun))
487 488
    }

A
Alex Crichton 已提交
489 490
    pub fn write_test_start(&mut self, test: &TestDesc,
                            align: NamePadding) -> io::IoResult<()> {
491
        let name = test.padded_name(self.max_name_len, align);
A
Alex Crichton 已提交
492
        self.write_plain(format!("test {} ... ", name))
M
Marijn Haverbeke 已提交
493
    }
494

A
Alex Crichton 已提交
495
    pub fn write_result(&mut self, result: &TestResult) -> io::IoResult<()> {
A
Alex Crichton 已提交
496
        try!(match *result {
497 498 499
            TrOk => self.write_ok(),
            TrFailed => self.write_failed(),
            TrIgnored => self.write_ignored(),
500
            TrMetrics(ref mm) => {
A
Alex Crichton 已提交
501
                try!(self.write_metric());
A
Alex Crichton 已提交
502
                self.write_plain(format!(": {}", fmt_metrics(mm)))
503
            }
504
            TrBench(ref bs) => {
A
Alex Crichton 已提交
505
                try!(self.write_bench());
A
Alex Crichton 已提交
506
                self.write_plain(format!(": {}", fmt_bench_samples(bs)))
507
            }
A
Alex Crichton 已提交
508 509
        });
        self.write_plain("\n")
510
    }
511

A
Alex Crichton 已提交
512 513
    pub fn write_log(&mut self, test: &TestDesc,
                     result: &TestResult) -> io::IoResult<()> {
514
        match self.log_out {
A
Alex Crichton 已提交
515
            None => Ok(()),
L
Luqman Aden 已提交
516
            Some(ref mut o) => {
D
Derek Guenther 已提交
517
                let s = format!("{} {}\n", match *result {
L
Luqman Aden 已提交
518 519 520 521 522 523
                        TrOk => ~"ok",
                        TrFailed => ~"failed",
                        TrIgnored => ~"ignored",
                        TrMetrics(ref mm) => fmt_metrics(mm),
                        TrBench(ref bs) => fmt_bench_samples(bs)
                    }, test.name.to_str());
A
Alex Crichton 已提交
524
                o.write(s.as_bytes())
525 526
            }
        }
B
Brian Anderson 已提交
527 528
    }

A
Alex Crichton 已提交
529
    pub fn write_failures(&mut self) -> io::IoResult<()> {
A
Alex Crichton 已提交
530
        try!(self.write_plain("\nfailures:\n"));
531
        let mut failures = ~[];
532 533
        let mut fail_out  = ~"";
        for &(ref f, ref stdout) in self.failures.iter() {
534
            failures.push(f.name.to_str());
535 536 537 538 539 540 541 542 543
            if stdout.len() > 0 {
                fail_out.push_str(format!("---- {} stdout ----\n\t",
                                  f.name.to_str()));
                let output = str::from_utf8_lossy(*stdout);
                fail_out.push_str(output.as_slice().replace("\n", "\n\t"));
                fail_out.push_str("\n");
            }
        }
        if fail_out.len() > 0 {
A
Alex Crichton 已提交
544 545
            try!(self.write_plain("\n"));
            try!(self.write_plain(fail_out));
546
        }
547

A
Alex Crichton 已提交
548
        try!(self.write_plain("\nfailures:\n"));
549
        failures.sort();
D
Daniel Micay 已提交
550
        for name in failures.iter() {
A
Alex Crichton 已提交
551
            try!(self.write_plain(format!("    {}\n", name.to_str())));
552
        }
A
Alex Crichton 已提交
553
        Ok(())
554 555
    }

A
Alex Crichton 已提交
556
    pub fn write_metric_diff(&mut self, diff: &MetricDiff) -> io::IoResult<()> {
557 558 559 560 561 562
        let mut noise = 0;
        let mut improved = 0;
        let mut regressed = 0;
        let mut added = 0;
        let mut removed = 0;

D
Daniel Micay 已提交
563
        for (k, v) in diff.iter() {
564 565 566 567
            match *v {
                LikelyNoise => noise += 1,
                MetricAdded => {
                    added += 1;
A
Alex Crichton 已提交
568 569
                    try!(self.write_added());
                    try!(self.write_plain(format!(": {}\n", *k)));
570 571 572
                }
                MetricRemoved => {
                    removed += 1;
A
Alex Crichton 已提交
573 574
                    try!(self.write_removed());
                    try!(self.write_plain(format!(": {}\n", *k)));
575 576 577
                }
                Improvement(pct) => {
                    improved += 1;
A
Alex Crichton 已提交
578 579 580
                    try!(self.write_plain(format!(": {}", *k)));
                    try!(self.write_improved());
                    try!(self.write_plain(format!(" by {:.2f}%\n", pct as f64)));
581 582 583
                }
                Regression(pct) => {
                    regressed += 1;
A
Alex Crichton 已提交
584 585 586
                    try!(self.write_plain(format!(": {}", *k)));
                    try!(self.write_regressed());
                    try!(self.write_plain(format!(" by {:.2f}%\n", pct as f64)));
587 588 589
                }
            }
        }
A
Alex Crichton 已提交
590
        try!(self.write_plain(format!("result of ratchet: {} metrics added, \
A
Alex Crichton 已提交
591 592 593 594
                                        {} removed, {} improved, {} regressed, \
                                        {} noise\n",
                                       added, removed, improved, regressed,
                                       noise)));
595
        if regressed == 0 {
A
Alex Crichton 已提交
596
            try!(self.write_plain("updated ratchet file\n"));
597
        } else {
A
Alex Crichton 已提交
598
            try!(self.write_plain("left ratchet file untouched\n"));
599
        }
A
Alex Crichton 已提交
600
        Ok(())
601 602
    }

L
Luqman Aden 已提交
603
    pub fn write_run_finish(&mut self,
604
                            ratchet_metrics: &Option<Path>,
A
Alex Crichton 已提交
605
                            ratchet_pct: Option<f64>) -> io::IoResult<bool> {
606
        assert!(self.passed + self.failed + self.ignored + self.measured == self.total);
607 608 609 610

        let ratchet_success = match *ratchet_metrics {
            None => true,
            Some(ref pth) => {
A
Alex Crichton 已提交
611
                try!(self.write_plain(format!("\nusing metrics ratcher: {}\n",
A
Alex Crichton 已提交
612
                                        pth.display())));
613 614 615
                match ratchet_pct {
                    None => (),
                    Some(pct) =>
A
Alex Crichton 已提交
616
                        try!(self.write_plain(format!("with noise-tolerance \
A
Alex Crichton 已提交
617 618
                                                         forced to: {}%\n",
                                                        pct)))
619 620
                }
                let (diff, ok) = self.metrics.ratchet(pth, ratchet_pct);
A
Alex Crichton 已提交
621
                try!(self.write_metric_diff(&diff));
622 623 624 625 626 627
                ok
            }
        };

        let test_success = self.failed == 0u;
        if !test_success {
A
Alex Crichton 已提交
628
            try!(self.write_failures());
629
        }
630

631 632
        let success = ratchet_success && test_success;

A
Alex Crichton 已提交
633
        try!(self.write_plain("\ntest result: "));
634 635
        if success {
            // There's no parallelism at this point so it's safe to use color
A
Alex Crichton 已提交
636
            try!(self.write_ok());
637
        } else {
A
Alex Crichton 已提交
638
            try!(self.write_failed());
639
        }
L
Luqman Aden 已提交
640 641
        let s = format!(". {} passed; {} failed; {} ignored; {} measured\n\n",
                        self.passed, self.failed, self.ignored, self.measured);
A
Alex Crichton 已提交
642
        try!(self.write_plain(s));
A
Alex Crichton 已提交
643
        return Ok(success);
644
    }
645 646
}

647
pub fn fmt_metrics(mm: &MetricMap) -> ~str {
648
    let MetricMap(ref mm) = *mm;
649
    let v : ~[~str] = mm.iter()
A
Alex Crichton 已提交
650
        .map(|(k,v)| format!("{}: {} (+/- {})",
651
                          *k,
D
Daniel Micay 已提交
652 653
                          v.value as f64,
                          v.noise as f64))
654 655 656 657
        .collect();
    v.connect(", ")
}

658 659
pub fn fmt_bench_samples(bs: &BenchSamples) -> ~str {
    if bs.mb_s != 0 {
660
        format!("{:>9} ns/iter (+/- {}) = {} MB/s",
661 662 663 664
             bs.ns_iter_summ.median as uint,
             (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint,
             bs.mb_s)
    } else {
665
        format!("{:>9} ns/iter (+/- {})",
666 667
             bs.ns_iter_summ.median as uint,
             (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint)
668
    }
669 670 671 672
}

// A simple console test runner
pub fn run_tests_console(opts: &TestOpts,
A
Alex Crichton 已提交
673 674 675
                         tests: ~[TestDescAndFn]) -> io::IoResult<bool> {
    fn callback<T: Writer>(event: &TestEvent,
                           st: &mut ConsoleTestState<T>) -> io::IoResult<()> {
676
        debug!("callback(event={:?})", event);
677
        match (*event).clone() {
678
            TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
679
            TeWait(ref test, padding) => st.write_test_start(test, padding),
680
            TeResult(test, result, stdout) => {
A
Alex Crichton 已提交
681 682
                try!(st.write_log(&test, &result));
                try!(st.write_result(&result));
683 684 685
                match result {
                    TrOk => st.passed += 1,
                    TrIgnored => st.ignored += 1,
686 687
                    TrMetrics(mm) => {
                        let tname = test.name.to_str();
688
                        let MetricMap(mm) = mm;
D
Daniel Micay 已提交
689
                        for (k,v) in mm.iter() {
690 691 692 693 694
                            st.metrics.insert_metric(tname + "." + *k,
                                                     v.value, v.noise);
                        }
                        st.measured += 1
                    }
695 696 697 698
                    TrBench(bs) => {
                        st.metrics.insert_metric(test.name.to_str(),
                                                 bs.ns_iter_summ.median,
                                                 bs.ns_iter_summ.max - bs.ns_iter_summ.min);
699
                        st.measured += 1
700
                    }
701 702
                    TrFailed => {
                        st.failed += 1;
703
                        st.failures.push((test, stdout));
704 705
                    }
                }
A
Alex Crichton 已提交
706
                Ok(())
707 708
            }
        }
709
    }
A
Alex Crichton 已提交
710
    let mut st = try!(ConsoleTestState::new(opts, None::<StdWriter>));
711 712 713 714 715 716 717 718 719
    fn len_if_padded(t: &TestDescAndFn) -> uint {
        match t.testfn.padding() {
            PadNone => 0u,
            PadOnLeft | PadOnRight => t.desc.name.to_str().len(),
        }
    }
    match tests.iter().max_by(|t|len_if_padded(*t)) {
        Some(t) => {
            let n = t.desc.name.to_str();
720
            debug!("Setting max_name_len from: {}", n);
721 722
            st.max_name_len = n.len();
        },
723 724
        None => {}
    }
A
Alex Crichton 已提交
725
    try!(run_tests(opts, tests, |x| callback(&x, &mut st)));
726 727 728
    match opts.save_metrics {
        None => (),
        Some(ref pth) => {
A
Alex Crichton 已提交
729 730
            try!(st.metrics.save(pth));
            try!(st.write_plain(format!("\nmetrics saved to: {}",
A
Alex Crichton 已提交
731
                                          pth.display())));
732 733
        }
    }
734
    return st.write_run_finish(&opts.ratchet_metrics, opts.ratchet_noise_percent);
735 736 737 738
}

#[test]
fn should_sort_failures_before_printing_them() {
A
Alex Crichton 已提交
739
    use std::io::MemWriter;
A
Alex Crichton 已提交
740
    use std::str;
741

A
Alex Crichton 已提交
742 743 744 745 746
    let test_a = TestDesc {
        name: StaticTestName("a"),
        ignore: false,
        should_fail: false
    };
747

A
Alex Crichton 已提交
748 749 750 751 752
    let test_b = TestDesc {
        name: StaticTestName("b"),
        ignore: false,
        should_fail: false
    };
753

L
Luqman Aden 已提交
754
    let mut st = ConsoleTestState {
A
Alex Crichton 已提交
755
        log_out: None,
A
Alex Crichton 已提交
756
        out: Raw(MemWriter::new()),
A
Alex Crichton 已提交
757 758 759 760 761 762 763 764
        use_color: false,
        total: 0u,
        passed: 0u,
        failed: 0u,
        ignored: 0u,
        measured: 0u,
        max_name_len: 10u,
        metrics: MetricMap::new(),
765
        failures: ~[(test_b, ~[]), (test_a, ~[])]
766
    };
767

768
    st.write_failures().unwrap();
L
Luqman Aden 已提交
769
    let s = match st.out {
770
        Raw(ref m) => str::from_utf8_lossy(m.get_ref()),
A
Alex Crichton 已提交
771
        Pretty(_) => unreachable!()
L
Luqman Aden 已提交
772
    };
A
Alex Crichton 已提交
773

774 775
    let apos = s.as_slice().find_str("a").unwrap();
    let bpos = s.as_slice().find_str("b").unwrap();
P
Patrick Walton 已提交
776
    assert!(apos < bpos);
777 778
}

779
fn use_color() -> bool { return get_concurrency() == 1; }
780

781
#[deriving(Clone)]
B
Brian Anderson 已提交
782 783
enum TestEvent {
    TeFiltered(~[TestDesc]),
784
    TeWait(TestDesc, NamePadding),
785
    TeResult(TestDesc, TestResult, ~[u8] /* stdout */),
B
Brian Anderson 已提交
786 787
}

788
pub type MonitorMsg = (TestDesc, TestResult, ~[u8] /* stdout */);
789

790
fn run_tests(opts: &TestOpts,
791
             tests: ~[TestDescAndFn],
A
Alex Crichton 已提交
792
             callback: |e: TestEvent| -> io::IoResult<()>) -> io::IoResult<()> {
T
Tim Chevalier 已提交
793
    let filtered_tests = filter_tests(opts, tests);
794
    let filtered_descs = filtered_tests.map(|t| t.desc.clone());
T
Tim Chevalier 已提交
795

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

798
    let (filtered_tests, filtered_benchs_and_metrics) =
799 800 801 802 803 804
        filtered_tests.partition(|e| {
            match e.testfn {
                StaticTestFn(_) | DynTestFn(_) => true,
                _ => false
            }
        });
805

B
Brian Anderson 已提交
806 807
    // 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 已提交
808
    let concurrency = get_concurrency();
809
    debug!("using {} test tasks", concurrency);
810

811
    let mut remaining = filtered_tests;
812
    remaining.reverse();
813
    let mut pending = 0;
B
Brian Anderson 已提交
814

L
Liigo Zhuang 已提交
815
    let (p, ch) = Chan::<MonitorMsg>::new();
816

817 818
    while pending > 0 || !remaining.is_empty() {
        while pending < concurrency && !remaining.is_empty() {
819
            let test = remaining.pop().unwrap();
820
            if concurrency == 1 {
821 822 823
                // 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 已提交
824
                try!(callback(TeWait(test.desc.clone(), test.testfn.padding())));
825
            }
826
            run_test(!opts.run_tests, test, ch.clone());
827
            pending += 1;
B
Brian Anderson 已提交
828 829
        }

830
        let (desc, result, stdout) = p.recv();
831
        if concurrency != 1 {
A
Alex Crichton 已提交
832
            try!(callback(TeWait(desc.clone(), PadNone)));
833
        }
A
Alex Crichton 已提交
834
        try!(callback(TeResult(desc, result, stdout)));
835
        pending -= 1;
B
Brian Anderson 已提交
836
    }
837 838

    // All benchmarks run at the end, in serial.
839
    // (this includes metric fns)
840
    for b in filtered_benchs_and_metrics.move_iter() {
A
Alex Crichton 已提交
841
        try!(callback(TeWait(b.desc.clone(), b.testfn.padding())));
842
        run_test(!opts.run_benchmarks, b, ch.clone());
843
        let (test, result, stdout) = p.recv();
A
Alex Crichton 已提交
844
        try!(callback(TeResult(test, result, stdout)));
845
    }
A
Alex Crichton 已提交
846
    Ok(())
B
Brian Anderson 已提交
847 848
}

849
fn get_concurrency() -> uint {
850
    use std::rt;
851 852 853 854 855
    match os::getenv("RUST_TEST_TASKS") {
        Some(s) => {
            let opt_n: Option<uint> = FromStr::from_str(s);
            match opt_n {
                Some(n) if n > 0 => n,
856
                _ => fail!("RUST_TEST_TASKS is `{}`, should be a positive integer.", s)
857 858 859
            }
        }
        None => {
860
            rt::default_sched_threads()
861 862
        }
    }
863
}
864

865 866 867 868 869
pub fn filter_tests(
    opts: &TestOpts,
    tests: ~[TestDescAndFn]) -> ~[TestDescAndFn]
{
    let mut filtered = tests;
870

871
    // Remove tests that don't match the test filter
B
Brian Anderson 已提交
872
    filtered = if opts.filter.is_none() {
L
Luqman Aden 已提交
873
        filtered
874
    } else {
875
        let filter_str = match opts.filter {
876
          Some(ref f) => (*f).clone(),
877
          None => ~""
M
Marijn Haverbeke 已提交
878
        };
879

880 881
        fn filter_fn(test: TestDescAndFn, filter_str: &str) ->
            Option<TestDescAndFn> {
882
            if test.desc.name.to_str().contains(filter_str) {
883 884 885 886
                return Some(test);
            } else {
                return None;
            }
887 888
        }

889
        filtered.move_iter().filter_map(|x| filter_fn(x, filter_str)).collect()
890 891
    };

892
    // Maybe pull out the ignored test and unignore them
893
    filtered = if !opts.run_ignored {
L
Luqman Aden 已提交
894
        filtered
895
    } else {
896 897 898 899 900 901 902 903 904 905
        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
            }
906
        };
907
        filtered.move_iter().filter_map(|x| filter(x)).collect()
908 909
    };

910
    // Sort the tests alphabetically
911
    filtered.sort_by(|t1, t2| t1.desc.name.to_str().cmp(&t2.desc.name.to_str()));
912

913 914 915 916 917 918 919 920 921
    // Shard the remaining tests, if sharding requested.
    match opts.test_shard {
        None => filtered,
        Some((a,b)) =>
            filtered.move_iter().enumerate()
            .filter(|&(i,_)| i % b == a)
            .map(|(_,t)| t)
            .to_owned_vec()
    }
922
}
923

924 925
pub fn run_test(force_ignore: bool,
                test: TestDescAndFn,
926
                monitor_ch: Chan<MonitorMsg>) {
927

928 929
    let TestDescAndFn {desc, testfn} = test;

930
    if force_ignore || desc.ignore {
931
        monitor_ch.send((desc, TrIgnored, ~[]));
B
Brian Anderson 已提交
932
        return;
933 934
    }

935
    fn run_test_inner(desc: TestDesc,
936
                      monitor_ch: Chan<MonitorMsg>,
937
                      testfn: proc()) {
938
        spawn(proc() {
939 940 941 942
            let (p, c) = Chan::new();
            let mut reader = PortReader::new(p);
            let stdout = ChanWriter::new(c.clone());
            let stderr = ChanWriter::new(c);
943 944 945 946
            let mut task = task::task().named(match desc.name {
                DynTestName(ref name) => name.clone().into_maybe_owned(),
                StaticTestName(name) => name.into_maybe_owned(),
            });
947 948
            task.opts.stdout = Some(~stdout as ~Writer);
            task.opts.stderr = Some(~stderr as ~Writer);
949
            let result_future = task.future_result();
950
            task.spawn(testfn);
951

952
            let stdout = reader.read_to_end().unwrap();
953
            let task_result = result_future.recv();
954
            let test_result = calc_result(&desc, task_result.is_ok());
955 956
            monitor_ch.send((desc.clone(), test_result, stdout));
        })
957 958 959
    }

    match testfn {
960
        DynBenchFn(bencher) => {
L
Liigo Zhuang 已提交
961
            let bs = ::bench::benchmark(|harness| bencher.run(harness));
962
            monitor_ch.send((desc, TrBench(bs), ~[]));
963 964 965
            return;
        }
        StaticBenchFn(benchfn) => {
L
Liigo Zhuang 已提交
966
            let bs = ::bench::benchmark(|harness| benchfn(harness));
967
            monitor_ch.send((desc, TrBench(bs), ~[]));
968 969
            return;
        }
970 971 972
        DynMetricFn(f) => {
            let mut mm = MetricMap::new();
            f(&mut mm);
973
            monitor_ch.send((desc, TrMetrics(mm), ~[]));
974 975 976 977 978
            return;
        }
        StaticMetricFn(f) => {
            let mut mm = MetricMap::new();
            f(&mut mm);
979
            monitor_ch.send((desc, TrMetrics(mm), ~[]));
980 981
            return;
        }
982
        DynTestFn(f) => run_test_inner(desc, monitor_ch, f),
983
        StaticTestFn(f) => run_test_inner(desc, monitor_ch, proc() f())
984
    }
985 986
}

987
fn calc_result(desc: &TestDesc, task_succeeded: bool) -> TestResult {
988
    if task_succeeded {
989
        if desc.should_fail { TrFailed }
B
Brian Anderson 已提交
990
        else { TrOk }
991
    } else {
992
        if desc.should_fail { TrOk }
B
Brian Anderson 已提交
993
        else { TrFailed }
994
    }
995 996
}

997 998 999

impl ToJson for Metric {
    fn to_json(&self) -> json::Json {
1000
        let mut map = ~TreeMap::new();
1001 1002
        map.insert(~"value", json::Number(self.value));
        map.insert(~"noise", json::Number(self.noise));
1003 1004 1005 1006 1007 1008
        json::Object(map)
    }
}

impl MetricMap {

1009
    pub fn new() -> MetricMap {
1010 1011 1012 1013
        MetricMap(TreeMap::new())
    }

    /// Load MetricDiff from a file.
A
Alex Crichton 已提交
1014 1015 1016 1017 1018
    ///
    /// # Failure
    ///
    /// This function will fail if the path does not exist or the path does not
    /// contain a valid metric map.
1019
    pub fn load(p: &Path) -> MetricMap {
1020
        assert!(p.exists());
A
Alex Crichton 已提交
1021
        let mut f = File::open(p).unwrap();
1022
        let value = json::from_reader(&mut f as &mut io::Reader).unwrap();
1023
        let mut decoder = json::Decoder::new(value);
1024 1025 1026 1027
        MetricMap(Decodable::decode(&mut decoder))
    }

    /// Write MetricDiff to a file.
A
Alex Crichton 已提交
1028
    pub fn save(&self, p: &Path) -> io::IoResult<()> {
A
Alex Crichton 已提交
1029
        let mut file = try!(File::create(p));
1030 1031
        let MetricMap(ref map) = *self;
        map.to_json().to_pretty_writer(&mut file)
1032 1033
    }

1034 1035 1036 1037 1038 1039 1040
    /// 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,
1041
                          noise_pct: Option<f64>) -> MetricDiff {
1042
        let mut diff : MetricDiff = TreeMap::new();
1043 1044
        let MetricMap(ref selfmap) = *self;
        let MetricMap(ref old) = *old;
D
Daniel Micay 已提交
1045
        for (k, vold) in old.iter() {
1046
            let r = match selfmap.find(k) {
1047 1048 1049
                None => MetricRemoved,
                Some(v) => {
                    let delta = (v.value - vold.value);
1050 1051 1052 1053
                    let noise = match noise_pct {
                        None => f64::max(vold.noise.abs(), v.noise.abs()),
                        Some(pct) => vold.value * pct / 100.0
                    };
1054
                    if delta.abs() <= noise {
1055 1056
                        LikelyNoise
                    } else {
M
Michael Darakananda 已提交
1057
                        let pct = delta.abs() / cmp::max(vold.value, f64::EPSILON) * 100.0;
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
                        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)
                            }
                        }
                    }
                }
            };
1080
            diff.insert((*k).clone(), r);
1081
        }
1082 1083
        let MetricMap(ref map) = *self;
        for (k, _) in map.iter() {
1084
            if !diff.contains_key(k) {
1085
                diff.insert((*k).clone(), MetricAdded);
1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
            }
        }
        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
        };
1109 1110
        let MetricMap(ref mut map) = *self;
        map.insert(name.to_owned(), m);
1111 1112 1113 1114 1115 1116 1117 1118
    }

    /// 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.
1119
    pub fn ratchet(&self, p: &Path, pct: Option<f64>) -> (MetricDiff, bool) {
1120
        let old = if p.exists() {
1121 1122 1123 1124 1125
            MetricMap::load(p)
        } else {
            MetricMap::new()
        };

1126
        let diff : MetricDiff = self.compare_to_old(&old, pct);
1127
        let ok = diff.iter().all(|(_, v)| {
1128 1129 1130 1131
            match *v {
                Regression(_) => false,
                _ => true
            }
1132
        });
1133 1134

        if ok {
1135
            debug!("rewriting file '{:?}' with updated metrics", p);
A
Alex Crichton 已提交
1136
            self.save(p).unwrap();
1137 1138 1139 1140 1141 1142 1143 1144
        }
        return (diff, ok)
    }
}


// Benchmarking

H
Huon Wilson 已提交
1145
/// A function that is opaque to the optimizer, to allow benchmarks to
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156
/// 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))}
}


1157 1158
impl BenchHarness {
    /// Callback for benchmark functions to run in their body.
1159
    pub fn iter<T>(&mut self, inner: || -> T) {
1160 1161
        self.ns_start = precise_time_ns();
        let k = self.iterations;
D
Daniel Micay 已提交
1162
        for _ in range(0u64, k) {
1163
            black_box(inner());
1164
        }
1165 1166
        self.ns_end = precise_time_ns();
    }
1167

1168 1169 1170 1171 1172
    pub fn ns_elapsed(&mut self) -> u64 {
        if self.ns_start == 0 || self.ns_end == 0 {
            0
        } else {
            self.ns_end - self.ns_start
1173
        }
1174
    }
1175

1176 1177 1178 1179
    pub fn ns_per_iter(&mut self) -> u64 {
        if self.iterations == 0 {
            0
        } else {
M
Michael Darakananda 已提交
1180
            self.ns_elapsed() / cmp::max(self.iterations, 1)
1181
        }
1182
    }
1183

1184
    pub fn bench_n(&mut self, n: u64, f: |&mut BenchHarness|) {
1185
        self.iterations = n;
1186
        debug!("running benchmark for {} iterations",
1187 1188 1189
               n as uint);
        f(self);
    }
1190

1191
    // This is a more statistics-driven benchmark algorithm
1192
    pub fn auto_bench(&mut self, f: |&mut BenchHarness|) -> stats::Summary {
1193

1194 1195
        // Initial bench run to get ballpark figure.
        let mut n = 1_u64;
1196
        self.bench_n(n, |x| f(x));
1197

1198 1199 1200 1201 1202
        // 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 已提交
1203
            n = 1_000_000 / cmp::max(self.ns_per_iter(), 1);
1204
        }
1205 1206 1207 1208 1209 1210 1211 1212 1213
        // 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; }

        debug!("Initial run took {} ns, iter count that takes 1ms estimated as {}",
               self.ns_per_iter(), n);
1214

1215 1216
        let mut total_run = 0;
        let samples : &mut [f64] = [0.0_f64, ..50];
1217
        loop {
1218
            let loop_start = precise_time_ns();
1219

D
Daniel Micay 已提交
1220
            for p in samples.mut_iter() {
1221
                self.bench_n(n, |x| f(x));
1222
                *p = self.ns_per_iter() as f64;
1223 1224
            };

1225
            stats::winsorize(samples, 5.0);
1226
            let summ = stats::Summary::new(samples);
1227

D
Daniel Micay 已提交
1228
            for p in samples.mut_iter() {
1229
                self.bench_n(5 * n, |x| f(x));
1230 1231 1232 1233 1234 1235
                *p = self.ns_per_iter() as f64;
            };

            stats::winsorize(samples, 5.0);
            let summ5 = stats::Summary::new(samples);

1236
            debug!("{} samples, median {}, MAD={}, MADP={}",
1237
                   samples.len(),
D
Daniel Micay 已提交
1238 1239 1240
                   summ.median as f64,
                   summ.median_abs_dev as f64,
                   summ.median_abs_dev_pct as f64);
1241 1242 1243 1244

            let now = precise_time_ns();
            let loop_run = now - loop_start;

1245
            // If we've run for 100ms and seem to have converged to a
1246 1247 1248 1249 1250
            // stable median.
            if loop_run > 100_000_000 &&
                summ.median_abs_dev_pct < 1.0 &&
                summ.median - summ5.median < summ5.median_abs_dev {
                return summ5;
1251 1252
            }

1253
            total_run += loop_run;
1254 1255
            // Longest we ever run for is 3s.
            if total_run > 3_000_000_000 {
1256
                return summ5;
1257
            }
1258

1259
            n *= 2;
1260 1261
        }
    }
1262 1263 1264
}

pub mod bench {
M
Michael Darakananda 已提交
1265
    use std::cmp;
L
Liigo Zhuang 已提交
1266
    use super::{BenchHarness, BenchSamples};
1267

1268
    pub fn benchmark(f: |&mut BenchHarness|) -> BenchSamples {
1269 1270 1271 1272 1273 1274 1275
        let mut bs = BenchHarness {
            iterations: 0,
            ns_start: 0,
            ns_end: 0,
            bytes: 0
        };

1276
        let ns_iter_summ = bs.auto_bench(f);
1277

M
Michael Darakananda 已提交
1278
        let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
1279
        let iter_s = 1_000_000_000 / ns_iter;
1280 1281 1282
        let mb_s = (bs.bytes * iter_s) / 1_000_000;

        BenchSamples {
1283
            ns_iter_summ: ns_iter_summ,
1284 1285 1286 1287 1288
            mb_s: mb_s as uint
        }
    }
}

1289 1290
#[cfg(test)]
mod tests {
1291
    use test::{TrFailed, TrIgnored, TrOk, filter_tests, parse_opts,
L
Liigo Zhuang 已提交
1292
               TestDesc, TestDescAndFn, TestOpts, run_test,
1293 1294
               Metric, MetricMap, MetricAdded, MetricRemoved,
               Improvement, Regression, LikelyNoise,
1295
               StaticTestName, DynTestName, DynTestFn};
L
Liigo Zhuang 已提交
1296
    use extra::tempfile::TempDir;
1297

1298
    #[test]
1299
    pub fn do_not_run_ignored_tests() {
1300
        fn f() { fail!(); }
1301 1302
        let desc = TestDescAndFn {
            desc: TestDesc {
1303
                name: StaticTestName("whatever"),
1304 1305 1306
                ignore: true,
                should_fail: false
            },
1307
            testfn: DynTestFn(proc() f()),
1308
        };
1309
        let (p, ch) = Chan::new();
1310
        run_test(false, desc, ch);
1311
        let (_, res, _) = p.recv();
P
Patrick Walton 已提交
1312
        assert!(res != TrOk);
1313 1314 1315
    }

    #[test]
1316
    pub fn ignored_tests_result_in_ignored() {
1317
        fn f() { }
1318 1319
        let desc = TestDescAndFn {
            desc: TestDesc {
1320
                name: StaticTestName("whatever"),
1321 1322 1323
                ignore: true,
                should_fail: false
            },
1324
            testfn: DynTestFn(proc() f()),
1325
        };
1326
        let (p, ch) = Chan::new();
1327
        run_test(false, desc, ch);
1328
        let (_, res, _) = p.recv();
1329
        assert_eq!(res, TrIgnored);
1330 1331 1332
    }

    #[test]
1333
    fn test_should_fail() {
1334
        fn f() { fail!(); }
1335 1336
        let desc = TestDescAndFn {
            desc: TestDesc {
1337
                name: StaticTestName("whatever"),
1338 1339 1340
                ignore: false,
                should_fail: true
            },
1341
            testfn: DynTestFn(proc() f()),
1342
        };
1343
        let (p, ch) = Chan::new();
1344
        run_test(false, desc, ch);
1345
        let (_, res, _) = p.recv();
1346
        assert_eq!(res, TrOk);
1347 1348 1349
    }

    #[test]
1350
    fn test_should_fail_but_succeeds() {
1351
        fn f() { }
1352 1353
        let desc = TestDescAndFn {
            desc: TestDesc {
1354
                name: StaticTestName("whatever"),
1355 1356 1357
                ignore: false,
                should_fail: true
            },
1358
            testfn: DynTestFn(proc() f()),
1359
        };
1360
        let (p, ch) = Chan::new();
1361
        run_test(false, desc, ch);
1362
        let (_, res, _) = p.recv();
1363
        assert_eq!(res, TrFailed);
1364 1365 1366
    }

    #[test]
1367
    fn first_free_arg_should_be_a_filter() {
1368
        let args = ~[~"progname", ~"filter"];
1369
        let opts = match parse_opts(args) {
B
Brian Anderson 已提交
1370
            Some(Ok(o)) => o,
1371
            _ => fail!("Malformed arg in first_free_arg_should_be_a_filter")
B
Brian Anderson 已提交
1372
        };
1373
        assert!("filter" == opts.filter.clone().unwrap());
1374 1375 1376
    }

    #[test]
1377
    fn parse_ignored_flag() {
1378
        let args = ~[~"progname", ~"filter", ~"--ignored"];
1379
        let opts = match parse_opts(args) {
B
Brian Anderson 已提交
1380
            Some(Ok(o)) => o,
1381
            _ => fail!("Malformed arg in parse_ignored_flag")
B
Brian Anderson 已提交
1382
        };
P
Patrick Walton 已提交
1383
        assert!((opts.run_ignored));
1384 1385 1386
    }

    #[test]
1387
    pub fn filter_for_ignored_option() {
1388 1389 1390
        // 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

1391
        let opts = TestOpts {
1392
            filter: None,
1393
            run_ignored: true,
1394
            logfile: None,
1395 1396
            run_tests: true,
            run_benchmarks: false,
1397 1398 1399
            ratchet_noise_percent: None,
            ratchet_metrics: None,
            save_metrics: None,
1400
            test_shard: None
1401 1402 1403
        };

        let tests = ~[
1404 1405
            TestDescAndFn {
                desc: TestDesc {
1406
                    name: StaticTestName("1"),
1407 1408 1409
                    ignore: true,
                    should_fail: false,
                },
1410
                testfn: DynTestFn(proc() {}),
1411
            },
1412 1413
            TestDescAndFn {
                desc: TestDesc {
1414
                    name: StaticTestName("2"),
1415 1416 1417
                    ignore: false,
                    should_fail: false
                },
1418
                testfn: DynTestFn(proc() {}),
1419 1420
            },
        ];
B
Brian Anderson 已提交
1421
        let filtered = filter_tests(&opts, tests);
1422

1423 1424 1425
        assert_eq!(filtered.len(), 1);
        assert_eq!(filtered[0].desc.name.to_str(), ~"1");
        assert!(filtered[0].desc.ignore == false);
1426 1427 1428
    }

    #[test]
1429
    pub fn sort_tests() {
1430
        let opts = TestOpts {
1431
            filter: None,
1432
            run_ignored: false,
1433
            logfile: None,
1434 1435
            run_tests: true,
            run_benchmarks: false,
1436 1437 1438
            ratchet_noise_percent: None,
            ratchet_metrics: None,
            save_metrics: None,
1439
            test_shard: None
1440
        };
1441 1442

        let names =
1443 1444 1445 1446 1447 1448
            ~[~"sha1::test", ~"int::test_to_str", ~"int::test_pow",
             ~"test::do_not_run_ignored_tests",
             ~"test::ignored_tests_result_in_ignored",
             ~"test::first_free_arg_should_be_a_filter",
             ~"test::parse_ignored_flag", ~"test::filter_for_ignored_option",
             ~"test::sort_tests"];
1449 1450
        let tests =
        {
1451
            fn testfn() { }
1452
            let mut tests = ~[];
D
Daniel Micay 已提交
1453
            for name in names.iter() {
1454 1455
                let test = TestDescAndFn {
                    desc: TestDesc {
1456
                        name: DynTestName((*name).clone()),
1457
                        ignore: false,
1458 1459
                        should_fail: false
                    },
P
Patrick Walton 已提交
1460
                    testfn: DynTestFn(testfn),
1461
                };
L
Luqman Aden 已提交
1462
                tests.push(test);
1463
            }
L
Luqman Aden 已提交
1464
            tests
1465
        };
B
Brian Anderson 已提交
1466
        let filtered = filter_tests(&opts, tests);
1467

1468 1469 1470 1471 1472 1473 1474 1475
        let expected =
            ~[~"int::test_pow", ~"int::test_to_str", ~"sha1::test",
              ~"test::do_not_run_ignored_tests",
              ~"test::filter_for_ignored_option",
              ~"test::first_free_arg_should_be_a_filter",
              ~"test::ignored_tests_result_in_ignored",
              ~"test::parse_ignored_flag",
              ~"test::sort_tests"];
1476

1477 1478
        for (a, b) in expected.iter().zip(filtered.iter()) {
            assert!(*a == b.desc.name.to_str());
1479 1480
        }
    }
1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505

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

1506 1507 1508 1509
        assert_eq!(*(diff1.find(&~"in-both-noise").unwrap()), LikelyNoise);
        assert_eq!(*(diff1.find(&~"in-first-noise").unwrap()), MetricRemoved);
        assert_eq!(*(diff1.find(&~"in-second-noise").unwrap()), MetricAdded);
        assert_eq!(*(diff1.find(&~"in-both-want-downwards-but-regressed").unwrap()),
1510
                   Regression(100.0));
1511
        assert_eq!(*(diff1.find(&~"in-both-want-downwards-and-improved").unwrap()),
1512
                   Improvement(50.0));
1513
        assert_eq!(*(diff1.find(&~"in-both-want-upwards-but-regressed").unwrap()),
1514
                   Regression(50.0));
1515
        assert_eq!(*(diff1.find(&~"in-both-want-upwards-and-improved").unwrap()),
1516
                   Improvement(100.0));
1517 1518 1519 1520
        assert_eq!(diff1.len(), 7);

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

1521 1522 1523 1524 1525 1526 1527
        assert_eq!(*(diff2.find(&~"in-both-noise").unwrap()), LikelyNoise);
        assert_eq!(*(diff2.find(&~"in-first-noise").unwrap()), MetricRemoved);
        assert_eq!(*(diff2.find(&~"in-second-noise").unwrap()), MetricAdded);
        assert_eq!(*(diff2.find(&~"in-both-want-downwards-but-regressed").unwrap()), LikelyNoise);
        assert_eq!(*(diff2.find(&~"in-both-want-downwards-and-improved").unwrap()), LikelyNoise);
        assert_eq!(*(diff2.find(&~"in-both-want-upwards-but-regressed").unwrap()), LikelyNoise);
        assert_eq!(*(diff2.find(&~"in-both-want-upwards-and-improved").unwrap()), LikelyNoise);
1528 1529 1530
        assert_eq!(diff2.len(), 7);
    }

1531
    #[test]
1532 1533
    pub fn ratchet_test() {

1534
        let dpth = TempDir::new("test-ratchet").expect("missing test for ratchet");
1535
        let pth = dpth.path().join("ratchet.json");
1536 1537 1538 1539 1540 1541 1542 1543 1544

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

1545
        m1.save(&pth).unwrap();
1546 1547 1548 1549 1550

        // 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);
1551 1552
        assert_eq!(*(diff1.find(&~"runtime").unwrap()), Regression(10.0));
        assert_eq!(*(diff1.find(&~"throughput").unwrap()), LikelyNoise);
1553 1554 1555

        // Check that it was not rewritten.
        let m3 = MetricMap::load(&pth);
1556
        let MetricMap(m3) = m3;
1557
        assert_eq!(m3.len(), 2);
L
Liigo Zhuang 已提交
1558 1559
        assert_eq!(*(m3.find(&~"runtime").unwrap()), Metric::new(1000.0, 2.0));
        assert_eq!(*(m3.find(&~"throughput").unwrap()), Metric::new(50.0, 2.0));
1560 1561 1562 1563 1564 1565

        // 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);
1566 1567
        assert_eq!(*(diff2.find(&~"runtime").unwrap()), LikelyNoise);
        assert_eq!(*(diff2.find(&~"throughput").unwrap()), LikelyNoise);
1568 1569 1570

        // Check that it was rewritten.
        let m4 = MetricMap::load(&pth);
1571
        let MetricMap(m4) = m4;
1572
        assert_eq!(m4.len(), 2);
L
Liigo Zhuang 已提交
1573 1574
        assert_eq!(*(m4.find(&~"runtime").unwrap()), Metric::new(1100.0, 2.0));
        assert_eq!(*(m4.find(&~"throughput").unwrap()), Metric::new(50.0, 2.0));
1575
    }
1576
}
L
Liigo Zhuang 已提交
1577