]> git.lizzy.rs Git - rust.git/blob - src/libextra/test.rs
libextra: Remove @mut from term.
[rust.git] / src / libextra / test.rs
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 #[doc(hidden)];
12
13 // Support code for rustc's built in test runner generator. Currently,
14 // none of this is meant for users. It is intended to support the
15 // simplest interface possible for representing and running tests
16 // while providing a base that other test frameworks may build off of.
17
18
19 use getopts;
20 use getopts::groups;
21 use json::ToJson;
22 use json;
23 use serialize::Decodable;
24 use sort;
25 use stats::Stats;
26 use stats;
27 use term;
28 use time::precise_time_ns;
29 use treemap::TreeMap;
30
31 use std::clone::Clone;
32 use std::comm::{stream, SharedChan, GenericPort, GenericChan};
33 use std::io;
34 use std::io::File;
35 use std::io::Writer;
36 use std::io::stdio::StdWriter;
37 use std::task;
38 use std::to_str::ToStr;
39 use std::f64;
40 use std::os;
41
42
43 // The name of a test. By convention this follows the rules for rust
44 // paths; i.e. it should be a series of identifiers separated by double
45 // colons. This way if some test runner wants to arrange the tests
46 // hierarchically it may.
47
48 #[deriving(Clone)]
49 pub enum TestName {
50     StaticTestName(&'static str),
51     DynTestName(~str)
52 }
53 impl ToStr for TestName {
54     fn to_str(&self) -> ~str {
55         match (*self).clone() {
56             StaticTestName(s) => s.to_str(),
57             DynTestName(s) => s.to_str()
58         }
59     }
60 }
61
62 #[deriving(Clone)]
63 enum NamePadding { PadNone, PadOnLeft, PadOnRight }
64
65 impl TestDesc {
66     fn padded_name(&self, column_count: uint, align: NamePadding) -> ~str {
67         use std::num::Saturating;
68         let name = self.name.to_str();
69         let fill = column_count.saturating_sub(name.len());
70         let pad = " ".repeat(fill);
71         match align {
72             PadNone => name,
73             PadOnLeft => pad.append(name),
74             PadOnRight => name.append(pad),
75         }
76     }
77 }
78
79 /// Represents a benchmark function.
80 pub trait TDynBenchFn {
81     fn run(&self, harness: &mut BenchHarness);
82 }
83
84 // A function that runs a test. If the function returns successfully,
85 // the test succeeds; if the function fails then the test fails. We
86 // may need to come up with a more clever definition of test in order
87 // to support isolation of tests into tasks.
88 pub enum TestFn {
89     StaticTestFn(extern fn()),
90     StaticBenchFn(extern fn(&mut BenchHarness)),
91     StaticMetricFn(proc(&mut MetricMap)),
92     DynTestFn(proc()),
93     DynMetricFn(proc(&mut MetricMap)),
94     DynBenchFn(~TDynBenchFn)
95 }
96
97 impl TestFn {
98     fn padding(&self) -> NamePadding {
99         match self {
100             &StaticTestFn(*)   => PadNone,
101             &StaticBenchFn(*)  => PadOnRight,
102             &StaticMetricFn(*) => PadOnRight,
103             &DynTestFn(*)      => PadNone,
104             &DynMetricFn(*)    => PadOnRight,
105             &DynBenchFn(*)     => PadOnRight,
106         }
107     }
108 }
109
110 // Structure passed to BenchFns
111 pub struct BenchHarness {
112     priv iterations: u64,
113     priv ns_start: u64,
114     priv ns_end: u64,
115     bytes: u64
116 }
117
118 // The definition of a single test. A test runner will run a list of
119 // these.
120 #[deriving(Clone)]
121 pub struct TestDesc {
122     name: TestName,
123     ignore: bool,
124     should_fail: bool
125 }
126
127 pub struct TestDescAndFn {
128     desc: TestDesc,
129     testfn: TestFn,
130 }
131
132 #[deriving(Clone, Encodable, Decodable, Eq)]
133 pub struct Metric {
134     priv value: f64,
135     priv noise: f64
136 }
137
138 #[deriving(Eq)]
139 pub struct MetricMap(TreeMap<~str,Metric>);
140
141 impl Clone for MetricMap {
142     fn clone(&self) -> MetricMap {
143         MetricMap((**self).clone())
144     }
145 }
146
147 /// Analysis of a single change in metric
148 #[deriving(Eq)]
149 pub enum MetricChange {
150     LikelyNoise,
151     MetricAdded,
152     MetricRemoved,
153     Improvement(f64),
154     Regression(f64)
155 }
156
157 pub type MetricDiff = TreeMap<~str,MetricChange>;
158
159 // The default console test runner. It accepts the command line
160 // arguments and a vector of test_descs.
161 pub fn test_main(args: &[~str], tests: ~[TestDescAndFn]) {
162     let opts =
163         match parse_opts(args) {
164             Some(Ok(o)) => o,
165             Some(Err(msg)) => fail!("{}", msg),
166             None => return
167         };
168     if !run_tests_console(&opts, tests) { fail!("Some tests failed"); }
169 }
170
171 // A variant optimized for invocation with a static test vector.
172 // This will fail (intentionally) when fed any dynamic tests, because
173 // it is copying the static values out into a dynamic vector and cannot
174 // copy dynamic values. It is doing this because from this point on
175 // a ~[TestDescAndFn] is used in order to effect ownership-transfer
176 // semantics into parallel test runners, which in turn requires a ~[]
177 // rather than a &[].
178 pub fn test_main_static(args: &[~str], tests: &[TestDescAndFn]) {
179     let owned_tests = do tests.map |t| {
180         match t.testfn {
181             StaticTestFn(f) =>
182             TestDescAndFn { testfn: StaticTestFn(f), desc: t.desc.clone() },
183
184             StaticBenchFn(f) =>
185             TestDescAndFn { testfn: StaticBenchFn(f), desc: t.desc.clone() },
186
187             _ => {
188                 fail!("non-static tests passed to test::test_main_static");
189             }
190         }
191     };
192     test_main(args, owned_tests)
193 }
194
195 pub struct TestOpts {
196     filter: Option<~str>,
197     run_ignored: bool,
198     run_tests: bool,
199     run_benchmarks: bool,
200     ratchet_metrics: Option<Path>,
201     ratchet_noise_percent: Option<f64>,
202     save_metrics: Option<Path>,
203     test_shard: Option<(uint,uint)>,
204     logfile: Option<Path>
205 }
206
207 type OptRes = Result<TestOpts, ~str>;
208
209 fn optgroups() -> ~[getopts::groups::OptGroup] {
210     ~[groups::optflag("", "ignored", "Run ignored tests"),
211       groups::optflag("", "test", "Run tests and not benchmarks"),
212       groups::optflag("", "bench", "Run benchmarks instead of tests"),
213       groups::optflag("h", "help", "Display this message (longer with --help)"),
214       groups::optopt("", "save-metrics", "Location to save bench metrics",
215                      "PATH"),
216       groups::optopt("", "ratchet-metrics",
217                      "Location to load and save metrics from. The metrics \
218                       loaded are cause benchmarks to fail if they run too \
219                       slowly", "PATH"),
220       groups::optopt("", "ratchet-noise-percent",
221                      "Tests within N% of the recorded metrics will be \
222                       considered as passing", "PERCENTAGE"),
223       groups::optopt("", "logfile", "Write logs to the specified file instead \
224                           of stdout", "PATH"),
225       groups::optopt("", "test-shard", "run shard A, of B shards, worth of the testsuite",
226                      "A.B")]
227 }
228
229 fn usage(binary: &str, helpstr: &str) {
230     let message = format!("Usage: {} [OPTIONS] [FILTER]", binary);
231     println(groups::usage(message, optgroups()));
232     println("");
233     if helpstr == "help" {
234         println("\
235 The FILTER is matched against the name of all tests to run, and if any tests
236 have a substring match, only those tests are run.
237
238 By default, all tests are run in parallel. This can be altered with the
239 RUST_TEST_TASKS environment variable when running tests (set it to 1).
240
241 Test Attributes:
242
243     #[test]        - Indicates a function is a test to be run. This function
244                      takes no arguments.
245     #[bench]       - Indicates a function is a benchmark to be run. This
246                      function takes one argument (extra::test::BenchHarness).
247     #[should_fail] - This function (also labeled with #[test]) will only pass if
248                      the code causes a failure (an assertion failure or fail!)
249     #[ignore]      - When applied to a function which is already attributed as a
250                      test, then the test runner will ignore these tests during
251                      normal test runs. Running with --ignored will run these
252                      tests. This may also be written as #[ignore(cfg(...))] to
253                      ignore the test on certain configurations.");
254     }
255 }
256
257 // Parses command line arguments into test options
258 pub fn parse_opts(args: &[~str]) -> Option<OptRes> {
259     let args_ = args.tail();
260     let matches =
261         match groups::getopts(args_, optgroups()) {
262           Ok(m) => m,
263           Err(f) => return Some(Err(f.to_err_msg()))
264         };
265
266     if matches.opt_present("h") { usage(args[0], "h"); return None; }
267     if matches.opt_present("help") { usage(args[0], "help"); return None; }
268
269     let filter =
270         if matches.free.len() > 0 {
271             Some((matches).free[0].clone())
272         } else {
273             None
274         };
275
276     let run_ignored = matches.opt_present("ignored");
277
278     let logfile = matches.opt_str("logfile");
279     let logfile = logfile.map(|s| Path::new(s));
280
281     let run_benchmarks = matches.opt_present("bench");
282     let run_tests = ! run_benchmarks ||
283         matches.opt_present("test");
284
285     let ratchet_metrics = matches.opt_str("ratchet-metrics");
286     let ratchet_metrics = ratchet_metrics.map(|s| Path::new(s));
287
288     let ratchet_noise_percent = matches.opt_str("ratchet-noise-percent");
289     let ratchet_noise_percent = ratchet_noise_percent.map(|s| from_str::<f64>(s).unwrap());
290
291     let save_metrics = matches.opt_str("save-metrics");
292     let save_metrics = save_metrics.map(|s| Path::new(s));
293
294     let test_shard = matches.opt_str("test-shard");
295     let test_shard = opt_shard(test_shard);
296
297     let test_opts = TestOpts {
298         filter: filter,
299         run_ignored: run_ignored,
300         run_tests: run_tests,
301         run_benchmarks: run_benchmarks,
302         ratchet_metrics: ratchet_metrics,
303         ratchet_noise_percent: ratchet_noise_percent,
304         save_metrics: save_metrics,
305         test_shard: test_shard,
306         logfile: logfile
307     };
308
309     Some(Ok(test_opts))
310 }
311
312 pub fn opt_shard(maybestr: Option<~str>) -> Option<(uint,uint)> {
313     match maybestr {
314         None => None,
315         Some(s) => {
316             match s.split_iter('.').to_owned_vec() {
317                 [a, b] => match (from_str::<uint>(a), from_str::<uint>(b)) {
318                     (Some(a), Some(b)) => Some((a,b)),
319                     _ => None
320                 },
321                 _ => None
322             }
323         }
324     }
325 }
326
327
328 #[deriving(Clone, Eq)]
329 pub struct BenchSamples {
330     priv ns_iter_summ: stats::Summary,
331     priv mb_s: uint
332 }
333
334 #[deriving(Clone, Eq)]
335 pub enum TestResult {
336     TrOk,
337     TrFailed,
338     TrIgnored,
339     TrMetrics(MetricMap),
340     TrBench(BenchSamples),
341 }
342
343 struct ConsoleTestState<T> {
344     log_out: Option<File>,
345     out: Either<term::Terminal<T>, T>,
346     use_color: bool,
347     total: uint,
348     passed: uint,
349     failed: uint,
350     ignored: uint,
351     measured: uint,
352     metrics: MetricMap,
353     failures: ~[TestDesc],
354     max_name_len: uint, // number of columns to fill when aligning names
355 }
356
357 impl<T: Writer> ConsoleTestState<T> {
358     pub fn new(opts: &TestOpts, _: Option<T>) -> ConsoleTestState<StdWriter> {
359         let log_out = match opts.logfile {
360             Some(ref path) => File::create(path),
361             None => None
362         };
363         let out = match term::Terminal::new(io::stdout()) {
364             Err(_) => Right(io::stdout()),
365             Ok(t) => Left(t)
366         };
367         ConsoleTestState {
368             out: out,
369             log_out: log_out,
370             use_color: use_color(),
371             total: 0u,
372             passed: 0u,
373             failed: 0u,
374             ignored: 0u,
375             measured: 0u,
376             metrics: MetricMap::new(),
377             failures: ~[],
378             max_name_len: 0u,
379         }
380     }
381
382     pub fn write_ok(&mut self) {
383         self.write_pretty("ok", term::color::GREEN);
384     }
385
386     pub fn write_failed(&mut self) {
387         self.write_pretty("FAILED", term::color::RED);
388     }
389
390     pub fn write_ignored(&mut self) {
391         self.write_pretty("ignored", term::color::YELLOW);
392     }
393
394     pub fn write_metric(&mut self) {
395         self.write_pretty("metric", term::color::CYAN);
396     }
397
398     pub fn write_bench(&mut self) {
399         self.write_pretty("bench", term::color::CYAN);
400     }
401
402     pub fn write_added(&mut self) {
403         self.write_pretty("added", term::color::GREEN);
404     }
405
406     pub fn write_improved(&mut self) {
407         self.write_pretty("improved", term::color::GREEN);
408     }
409
410     pub fn write_removed(&mut self) {
411         self.write_pretty("removed", term::color::YELLOW);
412     }
413
414     pub fn write_regressed(&mut self) {
415         self.write_pretty("regressed", term::color::RED);
416     }
417
418     pub fn write_pretty(&mut self,
419                         word: &str,
420                         color: term::color::Color) {
421         match self.out {
422             Left(ref mut term) => {
423                 if self.use_color {
424                     term.fg(color);
425                 }
426                 term.write(word.as_bytes());
427                 if self.use_color {
428                     term.reset();
429                 }
430             }
431             Right(ref mut stdout) => stdout.write(word.as_bytes())
432         }
433     }
434
435     pub fn write_plain(&mut self, s: &str) {
436         match self.out {
437             Left(ref mut term) => term.write(s.as_bytes()),
438             Right(ref mut stdout) => stdout.write(s.as_bytes())
439         }
440     }
441
442     pub fn write_run_start(&mut self, len: uint) {
443         self.total = len;
444         let noun = if len != 1 { &"tests" } else { &"test" };
445         self.write_plain(format!("\nrunning {} {}\n", len, noun));
446     }
447
448     pub fn write_test_start(&mut self, test: &TestDesc, align: NamePadding) {
449         let name = test.padded_name(self.max_name_len, align);
450         self.write_plain(format!("test {} ... ", name));
451     }
452
453     pub fn write_result(&mut self, result: &TestResult) {
454         match *result {
455             TrOk => self.write_ok(),
456             TrFailed => self.write_failed(),
457             TrIgnored => self.write_ignored(),
458             TrMetrics(ref mm) => {
459                 self.write_metric();
460                 self.write_plain(format!(": {}", fmt_metrics(mm)));
461             }
462             TrBench(ref bs) => {
463                 self.write_bench();
464                 self.write_plain(format!(": {}", fmt_bench_samples(bs)));
465             }
466         }
467         self.write_plain("\n");
468     }
469
470     pub fn write_log(&mut self, test: &TestDesc, result: &TestResult) {
471         match self.log_out {
472             None => (),
473             Some(ref mut o) => {
474                 let s = format!("{} {}", match *result {
475                         TrOk => ~"ok",
476                         TrFailed => ~"failed",
477                         TrIgnored => ~"ignored",
478                         TrMetrics(ref mm) => fmt_metrics(mm),
479                         TrBench(ref bs) => fmt_bench_samples(bs)
480                     }, test.name.to_str());
481                 o.write(s.as_bytes());
482             }
483         }
484     }
485
486     pub fn write_failures(&mut self) {
487         self.write_plain("\nfailures:\n");
488         let mut failures = ~[];
489         for f in self.failures.iter() {
490             failures.push(f.name.to_str());
491         }
492         sort::tim_sort(failures);
493         for name in failures.iter() {
494             self.write_plain(format!("    {}\n", name.to_str()));
495         }
496     }
497
498     pub fn write_metric_diff(&mut self, diff: &MetricDiff) {
499         let mut noise = 0;
500         let mut improved = 0;
501         let mut regressed = 0;
502         let mut added = 0;
503         let mut removed = 0;
504
505         for (k, v) in diff.iter() {
506             match *v {
507                 LikelyNoise => noise += 1,
508                 MetricAdded => {
509                     added += 1;
510                     self.write_added();
511                     self.write_plain(format!(": {}\n", *k));
512                 }
513                 MetricRemoved => {
514                     removed += 1;
515                     self.write_removed();
516                     self.write_plain(format!(": {}\n", *k));
517                 }
518                 Improvement(pct) => {
519                     improved += 1;
520                     self.write_plain(format!(": {}", *k));
521                     self.write_improved();
522                     self.write_plain(format!(" by {:.2f}%\n", pct as f64));
523                 }
524                 Regression(pct) => {
525                     regressed += 1;
526                     self.write_plain(format!(": {}", *k));
527                     self.write_regressed();
528                     self.write_plain(format!(" by {:.2f}%\n", pct as f64));
529                 }
530             }
531         }
532         self.write_plain(format!("result of ratchet: {} matrics added, {} removed, \
533                                   {} improved, {} regressed, {} noise\n",
534                                  added, removed, improved, regressed, noise));
535         if regressed == 0 {
536             self.write_plain("updated ratchet file\n");
537         } else {
538             self.write_plain("left ratchet file untouched\n");
539         }
540     }
541
542     pub fn write_run_finish(&mut self,
543                             ratchet_metrics: &Option<Path>,
544                             ratchet_pct: Option<f64>) -> bool {
545         assert!(self.passed + self.failed + self.ignored + self.measured == self.total);
546
547         let ratchet_success = match *ratchet_metrics {
548             None => true,
549             Some(ref pth) => {
550                 self.write_plain(format!("\nusing metrics ratcher: {}\n", pth.display()));
551                 match ratchet_pct {
552                     None => (),
553                     Some(pct) =>
554                         self.write_plain(format!("with noise-tolerance forced to: {}%\n",
555                                                  pct))
556                 }
557                 let (diff, ok) = self.metrics.ratchet(pth, ratchet_pct);
558                 self.write_metric_diff(&diff);
559                 ok
560             }
561         };
562
563         let test_success = self.failed == 0u;
564         if !test_success {
565             self.write_failures();
566         }
567
568         let success = ratchet_success && test_success;
569
570         self.write_plain("\ntest result: ");
571         if success {
572             // There's no parallelism at this point so it's safe to use color
573             self.write_ok();
574         } else {
575             self.write_failed();
576         }
577         let s = format!(". {} passed; {} failed; {} ignored; {} measured\n\n",
578                         self.passed, self.failed, self.ignored, self.measured);
579         self.write_plain(s);
580         return success;
581     }
582 }
583
584 pub fn fmt_metrics(mm: &MetricMap) -> ~str {
585     let v : ~[~str] = mm.iter()
586         .map(|(k,v)| format!("{}: {} (+/- {})",
587                           *k,
588                           v.value as f64,
589                           v.noise as f64))
590         .collect();
591     v.connect(", ")
592 }
593
594 pub fn fmt_bench_samples(bs: &BenchSamples) -> ~str {
595     if bs.mb_s != 0 {
596         format!("{:>9} ns/iter (+/- {}) = {} MB/s",
597              bs.ns_iter_summ.median as uint,
598              (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint,
599              bs.mb_s)
600     } else {
601         format!("{:>9} ns/iter (+/- {})",
602              bs.ns_iter_summ.median as uint,
603              (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint)
604     }
605 }
606
607 // A simple console test runner
608 pub fn run_tests_console(opts: &TestOpts,
609                          tests: ~[TestDescAndFn]) -> bool {
610     fn callback<T: Writer>(event: &TestEvent, st: &mut ConsoleTestState<T>) {
611         debug!("callback(event={:?})", event);
612         match (*event).clone() {
613             TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
614             TeWait(ref test, padding) => st.write_test_start(test, padding),
615             TeResult(test, result) => {
616                 st.write_log(&test, &result);
617                 st.write_result(&result);
618                 match result {
619                     TrOk => st.passed += 1,
620                     TrIgnored => st.ignored += 1,
621                     TrMetrics(mm) => {
622                         let tname = test.name.to_str();
623                         for (k,v) in mm.iter() {
624                             st.metrics.insert_metric(tname + "." + *k,
625                                                      v.value, v.noise);
626                         }
627                         st.measured += 1
628                     }
629                     TrBench(bs) => {
630                         st.metrics.insert_metric(test.name.to_str(),
631                                                  bs.ns_iter_summ.median,
632                                                  bs.ns_iter_summ.max - bs.ns_iter_summ.min);
633                         st.measured += 1
634                     }
635                     TrFailed => {
636                         st.failed += 1;
637                         st.failures.push(test);
638                     }
639                 }
640             }
641         }
642     }
643     let mut st = ConsoleTestState::new(opts, None::<StdWriter>);
644     fn len_if_padded(t: &TestDescAndFn) -> uint {
645         match t.testfn.padding() {
646             PadNone => 0u,
647             PadOnLeft | PadOnRight => t.desc.name.to_str().len(),
648         }
649     }
650     match tests.iter().max_by(|t|len_if_padded(*t)) {
651         Some(t) => {
652             let n = t.desc.name.to_str();
653             debug!("Setting max_name_len from: {}", n);
654             st.max_name_len = n.len();
655         },
656         None => {}
657     }
658     run_tests(opts, tests, |x| callback(&x, &mut st));
659     match opts.save_metrics {
660         None => (),
661         Some(ref pth) => {
662             st.metrics.save(pth);
663             st.write_plain(format!("\nmetrics saved to: {}", pth.display()));
664         }
665     }
666     return st.write_run_finish(&opts.ratchet_metrics, opts.ratchet_noise_percent);
667 }
668
669 #[test]
670 fn should_sort_failures_before_printing_them() {
671     use std::io::Decorator;
672     use std::io::mem::MemWriter;
673     use std::str;
674     fn dummy() {}
675
676     let test_a = TestDesc {
677         name: StaticTestName("a"),
678         ignore: false,
679         should_fail: false
680     };
681
682     let test_b = TestDesc {
683         name: StaticTestName("b"),
684         ignore: false,
685         should_fail: false
686     };
687
688     let mut st = ConsoleTestState {
689         log_out: None,
690         out: Right(MemWriter::new()),
691         use_color: false,
692         total: 0u,
693         passed: 0u,
694         failed: 0u,
695         ignored: 0u,
696         measured: 0u,
697         max_name_len: 10u,
698         metrics: MetricMap::new(),
699         failures: ~[test_b, test_a]
700     };
701
702     st.write_failures();
703     let s = match st.out {
704         Right(ref m) => str::from_utf8(*m.inner_ref()),
705         Left(_) => unreachable!()
706     };
707
708     let apos = s.find_str("a").unwrap();
709     let bpos = s.find_str("b").unwrap();
710     assert!(apos < bpos);
711 }
712
713 fn use_color() -> bool { return get_concurrency() == 1; }
714
715 #[deriving(Clone)]
716 enum TestEvent {
717     TeFiltered(~[TestDesc]),
718     TeWait(TestDesc, NamePadding),
719     TeResult(TestDesc, TestResult),
720 }
721
722 type MonitorMsg = (TestDesc, TestResult);
723
724 fn run_tests(opts: &TestOpts,
725              tests: ~[TestDescAndFn],
726              callback: |e: TestEvent|) {
727     let filtered_tests = filter_tests(opts, tests);
728     let filtered_descs = filtered_tests.map(|t| t.desc.clone());
729
730     callback(TeFiltered(filtered_descs));
731
732     let (filtered_tests, filtered_benchs_and_metrics) =
733         do filtered_tests.partition |e| {
734         match e.testfn {
735             StaticTestFn(_) | DynTestFn(_) => true,
736             _ => false
737         }
738     };
739
740     // It's tempting to just spawn all the tests at once, but since we have
741     // many tests that run in other processes we would be making a big mess.
742     let concurrency = get_concurrency();
743     debug!("using {} test tasks", concurrency);
744
745     let mut remaining = filtered_tests;
746     remaining.reverse();
747     let mut pending = 0;
748
749     let (p, ch) = stream();
750     let ch = SharedChan::new(ch);
751
752     while pending > 0 || !remaining.is_empty() {
753         while pending < concurrency && !remaining.is_empty() {
754             let test = remaining.pop();
755             if concurrency == 1 {
756                 // We are doing one test at a time so we can print the name
757                 // of the test before we run it. Useful for debugging tests
758                 // that hang forever.
759                 callback(TeWait(test.desc.clone(), test.testfn.padding()));
760             }
761             run_test(!opts.run_tests, test, ch.clone());
762             pending += 1;
763         }
764
765         let (desc, result) = p.recv();
766         if concurrency != 1 {
767             callback(TeWait(desc.clone(), PadNone));
768         }
769         callback(TeResult(desc, result));
770         pending -= 1;
771     }
772
773     // All benchmarks run at the end, in serial.
774     // (this includes metric fns)
775     for b in filtered_benchs_and_metrics.move_iter() {
776         callback(TeWait(b.desc.clone(), b.testfn.padding()));
777         run_test(!opts.run_benchmarks, b, ch.clone());
778         let (test, result) = p.recv();
779         callback(TeResult(test, result));
780     }
781 }
782
783 fn get_concurrency() -> uint {
784     use std::rt;
785     match os::getenv("RUST_TEST_TASKS") {
786         Some(s) => {
787             let opt_n: Option<uint> = FromStr::from_str(s);
788             match opt_n {
789                 Some(n) if n > 0 => n,
790                 _ => fail!("RUST_TEST_TASKS is `{}`, should be a positive integer.", s)
791             }
792         }
793         None => {
794             rt::default_sched_threads()
795         }
796     }
797 }
798
799 pub fn filter_tests(
800     opts: &TestOpts,
801     tests: ~[TestDescAndFn]) -> ~[TestDescAndFn]
802 {
803     let mut filtered = tests;
804
805     // Remove tests that don't match the test filter
806     filtered = if opts.filter.is_none() {
807         filtered
808     } else {
809         let filter_str = match opts.filter {
810           Some(ref f) => (*f).clone(),
811           None => ~""
812         };
813
814         fn filter_fn(test: TestDescAndFn, filter_str: &str) ->
815             Option<TestDescAndFn> {
816             if test.desc.name.to_str().contains(filter_str) {
817                 return Some(test);
818             } else {
819                 return None;
820             }
821         }
822
823         filtered.move_iter().filter_map(|x| filter_fn(x, filter_str)).collect()
824     };
825
826     // Maybe pull out the ignored test and unignore them
827     filtered = if !opts.run_ignored {
828         filtered
829     } else {
830         fn filter(test: TestDescAndFn) -> Option<TestDescAndFn> {
831             if test.desc.ignore {
832                 let TestDescAndFn {desc, testfn} = test;
833                 Some(TestDescAndFn {
834                     desc: TestDesc {ignore: false, ..desc},
835                     testfn: testfn
836                 })
837             } else {
838                 None
839             }
840         };
841         filtered.move_iter().filter_map(|x| filter(x)).collect()
842     };
843
844     // Sort the tests alphabetically
845     fn lteq(t1: &TestDescAndFn, t2: &TestDescAndFn) -> bool {
846         t1.desc.name.to_str() < t2.desc.name.to_str()
847     }
848     sort::quick_sort(filtered, lteq);
849
850     // Shard the remaining tests, if sharding requested.
851     match opts.test_shard {
852         None => filtered,
853         Some((a,b)) =>
854             filtered.move_iter().enumerate()
855             .filter(|&(i,_)| i % b == a)
856             .map(|(_,t)| t)
857             .to_owned_vec()
858     }
859 }
860
861 pub fn run_test(force_ignore: bool,
862                 test: TestDescAndFn,
863                 monitor_ch: SharedChan<MonitorMsg>) {
864
865     let TestDescAndFn {desc, testfn} = test;
866
867     if force_ignore || desc.ignore {
868         monitor_ch.send((desc, TrIgnored));
869         return;
870     }
871
872     fn run_test_inner(desc: TestDesc,
873                       monitor_ch: SharedChan<MonitorMsg>,
874                       testfn: proc()) {
875         let testfn_cell = ::std::cell::Cell::new(testfn);
876         do task::spawn {
877             let mut task = task::task();
878             task.unlinked();
879             task.name(match desc.name {
880                 DynTestName(ref name) => SendStrOwned(name.clone()),
881                 StaticTestName(name) => SendStrStatic(name),
882             });
883             let result_future = task.future_result();
884             task.spawn(testfn_cell.take());
885
886             let task_result = result_future.recv();
887             let test_result = calc_result(&desc, task_result.is_ok());
888             monitor_ch.send((desc.clone(), test_result));
889         }
890     }
891
892     match testfn {
893         DynBenchFn(bencher) => {
894             let bs = ::test::bench::benchmark(|harness| bencher.run(harness));
895             monitor_ch.send((desc, TrBench(bs)));
896             return;
897         }
898         StaticBenchFn(benchfn) => {
899             let bs = ::test::bench::benchmark(benchfn);
900             monitor_ch.send((desc, TrBench(bs)));
901             return;
902         }
903         DynMetricFn(f) => {
904             let mut mm = MetricMap::new();
905             f(&mut mm);
906             monitor_ch.send((desc, TrMetrics(mm)));
907             return;
908         }
909         StaticMetricFn(f) => {
910             let mut mm = MetricMap::new();
911             f(&mut mm);
912             monitor_ch.send((desc, TrMetrics(mm)));
913             return;
914         }
915         DynTestFn(f) => run_test_inner(desc, monitor_ch, f),
916         StaticTestFn(f) => run_test_inner(desc, monitor_ch, || f())
917     }
918 }
919
920 fn calc_result(desc: &TestDesc, task_succeeded: bool) -> TestResult {
921     if task_succeeded {
922         if desc.should_fail { TrFailed }
923         else { TrOk }
924     } else {
925         if desc.should_fail { TrOk }
926         else { TrFailed }
927     }
928 }
929
930
931 impl ToJson for Metric {
932     fn to_json(&self) -> json::Json {
933         let mut map = ~TreeMap::new();
934         map.insert(~"value", json::Number(self.value as f64));
935         map.insert(~"noise", json::Number(self.noise as f64));
936         json::Object(map)
937     }
938 }
939
940 impl MetricMap {
941
942     pub fn new() -> MetricMap {
943         MetricMap(TreeMap::new())
944     }
945
946     /// Load MetricDiff from a file.
947     pub fn load(p: &Path) -> MetricMap {
948         assert!(p.exists());
949         let f = @mut File::open(p) as @mut io::Reader;
950         let mut decoder = json::Decoder(json::from_reader(f).unwrap());
951         MetricMap(Decodable::decode(&mut decoder))
952     }
953
954     /// Write MetricDiff to a file.
955     pub fn save(&self, p: &Path) {
956         self.to_json().to_pretty_writer(@mut File::create(p) as @mut io::Writer);
957     }
958
959     /// Compare against another MetricMap. Optionally compare all
960     /// measurements in the maps using the provided `noise_pct` as a
961     /// percentage of each value to consider noise. If `None`, each
962     /// measurement's noise threshold is independently chosen as the
963     /// maximum of that measurement's recorded noise quantity in either
964     /// map.
965     pub fn compare_to_old(&self, old: &MetricMap,
966                           noise_pct: Option<f64>) -> MetricDiff {
967         let mut diff : MetricDiff = TreeMap::new();
968         for (k, vold) in old.iter() {
969             let r = match self.find(k) {
970                 None => MetricRemoved,
971                 Some(v) => {
972                     let delta = (v.value - vold.value);
973                     let noise = match noise_pct {
974                         None => f64::max(vold.noise.abs(), v.noise.abs()),
975                         Some(pct) => vold.value * pct / 100.0
976                     };
977                     if delta.abs() <= noise {
978                         LikelyNoise
979                     } else {
980                         let pct = delta.abs() / (vold.value).max(&f64::epsilon) * 100.0;
981                         if vold.noise < 0.0 {
982                             // When 'noise' is negative, it means we want
983                             // to see deltas that go up over time, and can
984                             // only tolerate slight negative movement.
985                             if delta < 0.0 {
986                                 Regression(pct)
987                             } else {
988                                 Improvement(pct)
989                             }
990                         } else {
991                             // When 'noise' is positive, it means we want
992                             // to see deltas that go down over time, and
993                             // can only tolerate slight positive movements.
994                             if delta < 0.0 {
995                                 Improvement(pct)
996                             } else {
997                                 Regression(pct)
998                             }
999                         }
1000                     }
1001                 }
1002             };
1003             diff.insert((*k).clone(), r);
1004         }
1005         for (k, _) in self.iter() {
1006             if !diff.contains_key(k) {
1007                 diff.insert((*k).clone(), MetricAdded);
1008             }
1009         }
1010         diff
1011     }
1012
1013     /// Insert a named `value` (+/- `noise`) metric into the map. The value
1014     /// must be non-negative. The `noise` indicates the uncertainty of the
1015     /// metric, which doubles as the "noise range" of acceptable
1016     /// pairwise-regressions on this named value, when comparing from one
1017     /// metric to the next using `compare_to_old`.
1018     ///
1019     /// If `noise` is positive, then it means this metric is of a value
1020     /// you want to see grow smaller, so a change larger than `noise` in the
1021     /// positive direction represents a regression.
1022     ///
1023     /// If `noise` is negative, then it means this metric is of a value
1024     /// you want to see grow larger, so a change larger than `noise` in the
1025     /// negative direction represents a regression.
1026     pub fn insert_metric(&mut self, name: &str, value: f64, noise: f64) {
1027         let m = Metric {
1028             value: value,
1029             noise: noise
1030         };
1031         self.insert(name.to_owned(), m);
1032     }
1033
1034     /// Attempt to "ratchet" an external metric file. This involves loading
1035     /// metrics from a metric file (if it exists), comparing against
1036     /// the metrics in `self` using `compare_to_old`, and rewriting the
1037     /// file to contain the metrics in `self` if none of the
1038     /// `MetricChange`s are `Regression`. Returns the diff as well
1039     /// as a boolean indicating whether the ratchet succeeded.
1040     pub fn ratchet(&self, p: &Path, pct: Option<f64>) -> (MetricDiff, bool) {
1041         let old = if p.exists() {
1042             MetricMap::load(p)
1043         } else {
1044             MetricMap::new()
1045         };
1046
1047         let diff : MetricDiff = self.compare_to_old(&old, pct);
1048         let ok = do diff.iter().all() |(_, v)| {
1049             match *v {
1050                 Regression(_) => false,
1051                 _ => true
1052             }
1053         };
1054
1055         if ok {
1056             debug!("rewriting file '{:?}' with updated metrics", p);
1057             self.save(p);
1058         }
1059         return (diff, ok)
1060     }
1061 }
1062
1063
1064 // Benchmarking
1065
1066 impl BenchHarness {
1067     /// Callback for benchmark functions to run in their body.
1068     pub fn iter(&mut self, inner: ||) {
1069         self.ns_start = precise_time_ns();
1070         let k = self.iterations;
1071         for _ in range(0u64, k) {
1072             inner();
1073         }
1074         self.ns_end = precise_time_ns();
1075     }
1076
1077     pub fn ns_elapsed(&mut self) -> u64 {
1078         if self.ns_start == 0 || self.ns_end == 0 {
1079             0
1080         } else {
1081             self.ns_end - self.ns_start
1082         }
1083     }
1084
1085     pub fn ns_per_iter(&mut self) -> u64 {
1086         if self.iterations == 0 {
1087             0
1088         } else {
1089             self.ns_elapsed() / self.iterations.max(&1)
1090         }
1091     }
1092
1093     pub fn bench_n(&mut self, n: u64, f: |&mut BenchHarness|) {
1094         self.iterations = n;
1095         debug!("running benchmark for {} iterations",
1096                n as uint);
1097         f(self);
1098     }
1099
1100     // This is a more statistics-driven benchmark algorithm
1101     pub fn auto_bench(&mut self, f: |&mut BenchHarness|) -> stats::Summary {
1102
1103         // Initial bench run to get ballpark figure.
1104         let mut n = 1_u64;
1105         self.bench_n(n, |x| f(x));
1106
1107         // Try to estimate iter count for 1ms falling back to 1m
1108         // iterations if first run took < 1ns.
1109         if self.ns_per_iter() == 0 {
1110             n = 1_000_000;
1111         } else {
1112             n = 1_000_000 / self.ns_per_iter().max(&1);
1113         }
1114
1115         let mut total_run = 0;
1116         let samples : &mut [f64] = [0.0_f64, ..50];
1117         loop {
1118             let loop_start = precise_time_ns();
1119
1120             for p in samples.mut_iter() {
1121                 self.bench_n(n as u64, |x| f(x));
1122                 *p = self.ns_per_iter() as f64;
1123             };
1124
1125             stats::winsorize(samples, 5.0);
1126             let summ = stats::Summary::new(samples);
1127
1128             for p in samples.mut_iter() {
1129                 self.bench_n(5 * n as u64, |x| f(x));
1130                 *p = self.ns_per_iter() as f64;
1131             };
1132
1133             stats::winsorize(samples, 5.0);
1134             let summ5 = stats::Summary::new(samples);
1135
1136             debug!("{} samples, median {}, MAD={}, MADP={}",
1137                    samples.len(),
1138                    summ.median as f64,
1139                    summ.median_abs_dev as f64,
1140                    summ.median_abs_dev_pct as f64);
1141
1142             let now = precise_time_ns();
1143             let loop_run = now - loop_start;
1144
1145             // If we've run for 100ms an seem to have converged to a
1146             // stable median.
1147             if loop_run > 100_000_000 &&
1148                 summ.median_abs_dev_pct < 1.0 &&
1149                 summ.median - summ5.median < summ5.median_abs_dev {
1150                 return summ5;
1151             }
1152
1153             total_run += loop_run;
1154             // Longest we ever run for is 3s.
1155             if total_run > 3_000_000_000 {
1156                 return summ5;
1157             }
1158
1159             n *= 2;
1160         }
1161     }
1162
1163
1164
1165
1166 }
1167
1168 pub mod bench {
1169     use test::{BenchHarness, BenchSamples};
1170
1171     pub fn benchmark(f: |&mut BenchHarness|) -> BenchSamples {
1172         let mut bs = BenchHarness {
1173             iterations: 0,
1174             ns_start: 0,
1175             ns_end: 0,
1176             bytes: 0
1177         };
1178
1179         let ns_iter_summ = bs.auto_bench(f);
1180
1181         let ns_iter = (ns_iter_summ.median as u64).max(&1);
1182         let iter_s = 1_000_000_000 / ns_iter;
1183         let mb_s = (bs.bytes * iter_s) / 1_000_000;
1184
1185         BenchSamples {
1186             ns_iter_summ: ns_iter_summ,
1187             mb_s: mb_s as uint
1188         }
1189     }
1190 }
1191
1192 #[cfg(test)]
1193 mod tests {
1194     use test::{TrFailed, TrIgnored, TrOk, filter_tests, parse_opts,
1195                TestDesc, TestDescAndFn,
1196                Metric, MetricMap, MetricAdded, MetricRemoved,
1197                Improvement, Regression, LikelyNoise,
1198                StaticTestName, DynTestName, DynTestFn};
1199     use test::{TestOpts, run_test};
1200
1201     use std::comm::{stream, SharedChan};
1202     use tempfile::TempDir;
1203
1204     #[test]
1205     pub fn do_not_run_ignored_tests() {
1206         fn f() { fail!(); }
1207         let desc = TestDescAndFn {
1208             desc: TestDesc {
1209                 name: StaticTestName("whatever"),
1210                 ignore: true,
1211                 should_fail: false
1212             },
1213             testfn: DynTestFn(|| f()),
1214         };
1215         let (p, ch) = stream();
1216         let ch = SharedChan::new(ch);
1217         run_test(false, desc, ch);
1218         let (_, res) = p.recv();
1219         assert!(res != TrOk);
1220     }
1221
1222     #[test]
1223     pub fn ignored_tests_result_in_ignored() {
1224         fn f() { }
1225         let desc = TestDescAndFn {
1226             desc: TestDesc {
1227                 name: StaticTestName("whatever"),
1228                 ignore: true,
1229                 should_fail: false
1230             },
1231             testfn: DynTestFn(|| f()),
1232         };
1233         let (p, ch) = stream();
1234         let ch = SharedChan::new(ch);
1235         run_test(false, desc, ch);
1236         let (_, res) = p.recv();
1237         assert_eq!(res, TrIgnored);
1238     }
1239
1240     #[test]
1241     fn test_should_fail() {
1242         fn f() { fail!(); }
1243         let desc = TestDescAndFn {
1244             desc: TestDesc {
1245                 name: StaticTestName("whatever"),
1246                 ignore: false,
1247                 should_fail: true
1248             },
1249             testfn: DynTestFn(|| f()),
1250         };
1251         let (p, ch) = stream();
1252         let ch = SharedChan::new(ch);
1253         run_test(false, desc, ch);
1254         let (_, res) = p.recv();
1255         assert_eq!(res, TrOk);
1256     }
1257
1258     #[test]
1259     fn test_should_fail_but_succeeds() {
1260         fn f() { }
1261         let desc = TestDescAndFn {
1262             desc: TestDesc {
1263                 name: StaticTestName("whatever"),
1264                 ignore: false,
1265                 should_fail: true
1266             },
1267             testfn: DynTestFn(|| f()),
1268         };
1269         let (p, ch) = stream();
1270         let ch = SharedChan::new(ch);
1271         run_test(false, desc, ch);
1272         let (_, res) = p.recv();
1273         assert_eq!(res, TrFailed);
1274     }
1275
1276     #[test]
1277     fn first_free_arg_should_be_a_filter() {
1278         let args = ~[~"progname", ~"filter"];
1279         let opts = match parse_opts(args) {
1280             Some(Ok(o)) => o,
1281             _ => fail!("Malformed arg in first_free_arg_should_be_a_filter")
1282         };
1283         assert!("filter" == opts.filter.clone().unwrap());
1284     }
1285
1286     #[test]
1287     fn parse_ignored_flag() {
1288         let args = ~[~"progname", ~"filter", ~"--ignored"];
1289         let opts = match parse_opts(args) {
1290             Some(Ok(o)) => o,
1291             _ => fail!("Malformed arg in parse_ignored_flag")
1292         };
1293         assert!((opts.run_ignored));
1294     }
1295
1296     #[test]
1297     pub fn filter_for_ignored_option() {
1298         fn dummy() {}
1299
1300         // When we run ignored tests the test filter should filter out all the
1301         // unignored tests and flip the ignore flag on the rest to false
1302
1303         let opts = TestOpts {
1304             filter: None,
1305             run_ignored: true,
1306             logfile: None,
1307             run_tests: true,
1308             run_benchmarks: false,
1309             ratchet_noise_percent: None,
1310             ratchet_metrics: None,
1311             save_metrics: None,
1312             test_shard: None
1313         };
1314
1315         let tests = ~[
1316             TestDescAndFn {
1317                 desc: TestDesc {
1318                     name: StaticTestName("1"),
1319                     ignore: true,
1320                     should_fail: false,
1321                 },
1322                 testfn: DynTestFn(|| {}),
1323             },
1324             TestDescAndFn {
1325                 desc: TestDesc {
1326                     name: StaticTestName("2"),
1327                     ignore: false,
1328                     should_fail: false
1329                 },
1330                 testfn: DynTestFn(|| {}),
1331             },
1332         ];
1333         let filtered = filter_tests(&opts, tests);
1334
1335         assert_eq!(filtered.len(), 1);
1336         assert_eq!(filtered[0].desc.name.to_str(), ~"1");
1337         assert!(filtered[0].desc.ignore == false);
1338     }
1339
1340     #[test]
1341     pub fn sort_tests() {
1342         let opts = TestOpts {
1343             filter: None,
1344             run_ignored: false,
1345             logfile: None,
1346             run_tests: true,
1347             run_benchmarks: false,
1348             ratchet_noise_percent: None,
1349             ratchet_metrics: None,
1350             save_metrics: None,
1351             test_shard: None
1352         };
1353
1354         let names =
1355             ~[~"sha1::test", ~"int::test_to_str", ~"int::test_pow",
1356              ~"test::do_not_run_ignored_tests",
1357              ~"test::ignored_tests_result_in_ignored",
1358              ~"test::first_free_arg_should_be_a_filter",
1359              ~"test::parse_ignored_flag", ~"test::filter_for_ignored_option",
1360              ~"test::sort_tests"];
1361         let tests =
1362         {
1363             fn testfn() { }
1364             let mut tests = ~[];
1365             for name in names.iter() {
1366                 let test = TestDescAndFn {
1367                     desc: TestDesc {
1368                         name: DynTestName((*name).clone()),
1369                         ignore: false,
1370                         should_fail: false
1371                     },
1372                     testfn: DynTestFn(testfn),
1373                 };
1374                 tests.push(test);
1375             }
1376             tests
1377         };
1378         let filtered = filter_tests(&opts, tests);
1379
1380         let expected =
1381             ~[~"int::test_pow", ~"int::test_to_str", ~"sha1::test",
1382               ~"test::do_not_run_ignored_tests",
1383               ~"test::filter_for_ignored_option",
1384               ~"test::first_free_arg_should_be_a_filter",
1385               ~"test::ignored_tests_result_in_ignored",
1386               ~"test::parse_ignored_flag",
1387               ~"test::sort_tests"];
1388
1389         for (a, b) in expected.iter().zip(filtered.iter()) {
1390             assert!(*a == b.desc.name.to_str());
1391         }
1392     }
1393
1394     #[test]
1395     pub fn test_metricmap_compare() {
1396         let mut m1 = MetricMap::new();
1397         let mut m2 = MetricMap::new();
1398         m1.insert_metric("in-both-noise", 1000.0, 200.0);
1399         m2.insert_metric("in-both-noise", 1100.0, 200.0);
1400
1401         m1.insert_metric("in-first-noise", 1000.0, 2.0);
1402         m2.insert_metric("in-second-noise", 1000.0, 2.0);
1403
1404         m1.insert_metric("in-both-want-downwards-but-regressed", 1000.0, 10.0);
1405         m2.insert_metric("in-both-want-downwards-but-regressed", 2000.0, 10.0);
1406
1407         m1.insert_metric("in-both-want-downwards-and-improved", 2000.0, 10.0);
1408         m2.insert_metric("in-both-want-downwards-and-improved", 1000.0, 10.0);
1409
1410         m1.insert_metric("in-both-want-upwards-but-regressed", 2000.0, -10.0);
1411         m2.insert_metric("in-both-want-upwards-but-regressed", 1000.0, -10.0);
1412
1413         m1.insert_metric("in-both-want-upwards-and-improved", 1000.0, -10.0);
1414         m2.insert_metric("in-both-want-upwards-and-improved", 2000.0, -10.0);
1415
1416         let diff1 = m2.compare_to_old(&m1, None);
1417
1418         assert_eq!(*(diff1.find(&~"in-both-noise").unwrap()), LikelyNoise);
1419         assert_eq!(*(diff1.find(&~"in-first-noise").unwrap()), MetricRemoved);
1420         assert_eq!(*(diff1.find(&~"in-second-noise").unwrap()), MetricAdded);
1421         assert_eq!(*(diff1.find(&~"in-both-want-downwards-but-regressed").unwrap()),
1422                    Regression(100.0));
1423         assert_eq!(*(diff1.find(&~"in-both-want-downwards-and-improved").unwrap()),
1424                    Improvement(50.0));
1425         assert_eq!(*(diff1.find(&~"in-both-want-upwards-but-regressed").unwrap()),
1426                    Regression(50.0));
1427         assert_eq!(*(diff1.find(&~"in-both-want-upwards-and-improved").unwrap()),
1428                    Improvement(100.0));
1429         assert_eq!(diff1.len(), 7);
1430
1431         let diff2 = m2.compare_to_old(&m1, Some(200.0));
1432
1433         assert_eq!(*(diff2.find(&~"in-both-noise").unwrap()), LikelyNoise);
1434         assert_eq!(*(diff2.find(&~"in-first-noise").unwrap()), MetricRemoved);
1435         assert_eq!(*(diff2.find(&~"in-second-noise").unwrap()), MetricAdded);
1436         assert_eq!(*(diff2.find(&~"in-both-want-downwards-but-regressed").unwrap()), LikelyNoise);
1437         assert_eq!(*(diff2.find(&~"in-both-want-downwards-and-improved").unwrap()), LikelyNoise);
1438         assert_eq!(*(diff2.find(&~"in-both-want-upwards-but-regressed").unwrap()), LikelyNoise);
1439         assert_eq!(*(diff2.find(&~"in-both-want-upwards-and-improved").unwrap()), LikelyNoise);
1440         assert_eq!(diff2.len(), 7);
1441     }
1442
1443     pub fn ratchet_test() {
1444
1445         let dpth = TempDir::new("test-ratchet").expect("missing test for ratchet");
1446         let pth = dpth.path().join("ratchet.json");
1447
1448         let mut m1 = MetricMap::new();
1449         m1.insert_metric("runtime", 1000.0, 2.0);
1450         m1.insert_metric("throughput", 50.0, 2.0);
1451
1452         let mut m2 = MetricMap::new();
1453         m2.insert_metric("runtime", 1100.0, 2.0);
1454         m2.insert_metric("throughput", 50.0, 2.0);
1455
1456         m1.save(&pth);
1457
1458         // Ask for a ratchet that should fail to advance.
1459         let (diff1, ok1) = m2.ratchet(&pth, None);
1460         assert_eq!(ok1, false);
1461         assert_eq!(diff1.len(), 2);
1462         assert_eq!(*(diff1.find(&~"runtime").unwrap()), Regression(10.0));
1463         assert_eq!(*(diff1.find(&~"throughput").unwrap()), LikelyNoise);
1464
1465         // Check that it was not rewritten.
1466         let m3 = MetricMap::load(&pth);
1467         assert_eq!(m3.len(), 2);
1468         assert_eq!(*(m3.find(&~"runtime").unwrap()), Metric { value: 1000.0, noise: 2.0 });
1469         assert_eq!(*(m3.find(&~"throughput").unwrap()), Metric { value: 50.0, noise: 2.0 });
1470
1471         // Ask for a ratchet with an explicit noise-percentage override,
1472         // that should advance.
1473         let (diff2, ok2) = m2.ratchet(&pth, Some(10.0));
1474         assert_eq!(ok2, true);
1475         assert_eq!(diff2.len(), 2);
1476         assert_eq!(*(diff2.find(&~"runtime").unwrap()), LikelyNoise);
1477         assert_eq!(*(diff2.find(&~"throughput").unwrap()), LikelyNoise);
1478
1479         // Check that it was rewritten.
1480         let m4 = MetricMap::load(&pth);
1481         assert_eq!(m4.len(), 2);
1482         assert_eq!(*(m4.find(&~"runtime").unwrap()), Metric { value: 1100.0, noise: 2.0 });
1483         assert_eq!(*(m4.find(&~"throughput").unwrap()), Metric { value: 50.0, noise: 2.0 });
1484     }
1485 }