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