|  | use criterion; | 
|  | use serde_json; | 
|  |  | 
|  | #[cfg(feature = "plotters")] | 
|  | use criterion::SamplingMode; | 
|  | use criterion::{ | 
|  | criterion_group, criterion_main, profiler::Profiler, BatchSize, BenchmarkId, Criterion, | 
|  | }; | 
|  | use serde_json::value::Value; | 
|  | use std::cell::{Cell, RefCell}; | 
|  | use std::cmp::max; | 
|  | use std::fs::File; | 
|  | use std::path::{Path, PathBuf}; | 
|  | use std::rc::Rc; | 
|  | use std::time::{Duration, SystemTime}; | 
|  | use tempfile::{tempdir, TempDir}; | 
|  | use walkdir::WalkDir; | 
|  |  | 
|  | /* | 
|  | * Please note that these tests are not complete examples of how to use | 
|  | * Criterion.rs. See the benches folder for actual examples. | 
|  | */ | 
|  | fn temp_dir() -> TempDir { | 
|  | tempdir().unwrap() | 
|  | } | 
|  |  | 
|  | // Configure a Criterion struct to perform really fast benchmarks. This is not | 
|  | // recommended for real benchmarking, only for testing. | 
|  | fn short_benchmark(dir: &TempDir) -> Criterion { | 
|  | Criterion::default() | 
|  | .output_directory(dir.path()) | 
|  | .warm_up_time(Duration::from_millis(250)) | 
|  | .measurement_time(Duration::from_millis(500)) | 
|  | .nresamples(2000) | 
|  | } | 
|  |  | 
|  | #[derive(Clone)] | 
|  | struct Counter { | 
|  | counter: Rc<RefCell<usize>>, | 
|  | } | 
|  | impl Counter { | 
|  | fn count(&self) { | 
|  | *(*self.counter).borrow_mut() += 1; | 
|  | } | 
|  |  | 
|  | fn read(&self) -> usize { | 
|  | *(*self.counter).borrow() | 
|  | } | 
|  | } | 
|  | impl Default for Counter { | 
|  | fn default() -> Counter { | 
|  | Counter { | 
|  | counter: Rc::new(RefCell::new(0)), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | fn verify_file(dir: &PathBuf, path: &str) -> PathBuf { | 
|  | let full_path = dir.join(path); | 
|  | assert!( | 
|  | full_path.is_file(), | 
|  | "File {:?} does not exist or is not a file", | 
|  | full_path | 
|  | ); | 
|  | let metadata = full_path.metadata().unwrap(); | 
|  | assert!(metadata.len() > 0); | 
|  | full_path | 
|  | } | 
|  |  | 
|  | fn verify_json(dir: &PathBuf, path: &str) { | 
|  | let full_path = verify_file(dir, path); | 
|  | let f = File::open(full_path).unwrap(); | 
|  | serde_json::from_reader::<File, Value>(f).unwrap(); | 
|  | } | 
|  |  | 
|  | #[cfg(feature = "html_reports")] | 
|  | fn verify_svg(dir: &PathBuf, path: &str) { | 
|  | verify_file(dir, path); | 
|  | } | 
|  |  | 
|  | #[cfg(feature = "html_reports")] | 
|  | fn verify_html(dir: &PathBuf, path: &str) { | 
|  | verify_file(dir, path); | 
|  | } | 
|  |  | 
|  | fn verify_stats(dir: &PathBuf, baseline: &str) { | 
|  | verify_json(&dir, &format!("{}/estimates.json", baseline)); | 
|  | verify_json(&dir, &format!("{}/sample.json", baseline)); | 
|  | verify_json(&dir, &format!("{}/tukey.json", baseline)); | 
|  | verify_json(&dir, &format!("{}/benchmark.json", baseline)); | 
|  | #[cfg(feature = "csv_output")] | 
|  | verify_file(&dir, &format!("{}/raw.csv", baseline)); | 
|  | } | 
|  |  | 
|  | fn verify_not_exists(dir: &PathBuf, path: &str) { | 
|  | assert!(!dir.join(path).exists()); | 
|  | } | 
|  |  | 
|  | fn latest_modified(dir: &PathBuf) -> SystemTime { | 
|  | let mut newest_update: Option<SystemTime> = None; | 
|  | for entry in WalkDir::new(dir) { | 
|  | let entry = entry.unwrap(); | 
|  | let modified = entry.metadata().unwrap().modified().unwrap(); | 
|  | newest_update = match newest_update { | 
|  | Some(latest) => Some(max(latest, modified)), | 
|  | None => Some(modified), | 
|  | }; | 
|  | } | 
|  |  | 
|  | newest_update.expect("failed to find a single time in directory") | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_creates_directory() { | 
|  | let dir = temp_dir(); | 
|  | short_benchmark(&dir).bench_function("test_creates_directory", |b| b.iter(|| 10)); | 
|  | assert!(dir.path().join("test_creates_directory").is_dir()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_without_plots() { | 
|  | let dir = temp_dir(); | 
|  | short_benchmark(&dir) | 
|  | .without_plots() | 
|  | .bench_function("test_without_plots", |b| b.iter(|| 10)); | 
|  |  | 
|  | for entry in WalkDir::new(dir.path().join("test_without_plots")) { | 
|  | let entry = entry.ok(); | 
|  | let is_svg = entry | 
|  | .as_ref() | 
|  | .and_then(|entry| entry.path().extension()) | 
|  | .and_then(|ext| ext.to_str()) | 
|  | .map(|ext| ext == "svg") | 
|  | .unwrap_or(false); | 
|  | assert!( | 
|  | !is_svg, | 
|  | "Found SVG file ({:?}) in output directory with plots disabled", | 
|  | entry.unwrap().file_name() | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_save_baseline() { | 
|  | let dir = temp_dir(); | 
|  | println!("tmp directory is {:?}", dir.path()); | 
|  | short_benchmark(&dir) | 
|  | .save_baseline("some-baseline".to_owned()) | 
|  | .bench_function("test_save_baseline", |b| b.iter(|| 10)); | 
|  |  | 
|  | let dir = dir.path().join("test_save_baseline"); | 
|  | verify_stats(&dir, "some-baseline"); | 
|  |  | 
|  | verify_not_exists(&dir, "base"); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_retain_baseline() { | 
|  | // Initial benchmark to populate | 
|  | let dir = temp_dir(); | 
|  | short_benchmark(&dir) | 
|  | .save_baseline("some-baseline".to_owned()) | 
|  | .bench_function("test_retain_baseline", |b| b.iter(|| 10)); | 
|  |  | 
|  | let pre_modified = latest_modified(&dir.path().join("test_retain_baseline/some-baseline")); | 
|  |  | 
|  | short_benchmark(&dir) | 
|  | .retain_baseline("some-baseline".to_owned(), true) | 
|  | .bench_function("test_retain_baseline", |b| b.iter(|| 10)); | 
|  |  | 
|  | let post_modified = latest_modified(&dir.path().join("test_retain_baseline/some-baseline")); | 
|  |  | 
|  | assert_eq!(pre_modified, post_modified, "baseline modified by retain"); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | #[should_panic(expected = "Baseline 'some-baseline' must exist before comparison is allowed")] | 
|  | fn test_compare_baseline_strict_panics_when_missing_baseline() { | 
|  | let dir = temp_dir(); | 
|  | short_benchmark(&dir) | 
|  | .retain_baseline("some-baseline".to_owned(), true) | 
|  | .bench_function("test_compare_baseline", |b| b.iter(|| 10)); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_compare_baseline_lenient_when_missing_baseline() { | 
|  | let dir = temp_dir(); | 
|  | short_benchmark(&dir) | 
|  | .retain_baseline("some-baseline".to_owned(), false) | 
|  | .bench_function("test_compare_baseline", |b| b.iter(|| 10)); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_sample_size() { | 
|  | let dir = temp_dir(); | 
|  | let counter = Counter::default(); | 
|  |  | 
|  | let clone = counter.clone(); | 
|  | short_benchmark(&dir) | 
|  | .sample_size(50) | 
|  | .bench_function("test_sample_size", move |b| { | 
|  | clone.count(); | 
|  | b.iter(|| 10) | 
|  | }); | 
|  |  | 
|  | // This function will be called more than sample_size times because of the | 
|  | // warmup. | 
|  | assert!(counter.read() > 50); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_warmup_time() { | 
|  | let dir = temp_dir(); | 
|  | let counter1 = Counter::default(); | 
|  |  | 
|  | let clone = counter1.clone(); | 
|  | short_benchmark(&dir) | 
|  | .warm_up_time(Duration::from_millis(100)) | 
|  | .bench_function("test_warmup_time_1", move |b| { | 
|  | clone.count(); | 
|  | b.iter(|| 10) | 
|  | }); | 
|  |  | 
|  | let counter2 = Counter::default(); | 
|  | let clone = counter2.clone(); | 
|  | short_benchmark(&dir) | 
|  | .warm_up_time(Duration::from_millis(2000)) | 
|  | .bench_function("test_warmup_time_2", move |b| { | 
|  | clone.count(); | 
|  | b.iter(|| 10) | 
|  | }); | 
|  |  | 
|  | assert!(counter1.read() < counter2.read()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_measurement_time() { | 
|  | let dir = temp_dir(); | 
|  | let counter1 = Counter::default(); | 
|  |  | 
|  | let clone = counter1.clone(); | 
|  | short_benchmark(&dir) | 
|  | .measurement_time(Duration::from_millis(100)) | 
|  | .bench_function("test_meas_time_1", move |b| b.iter(|| clone.count())); | 
|  |  | 
|  | let counter2 = Counter::default(); | 
|  | let clone = counter2.clone(); | 
|  | short_benchmark(&dir) | 
|  | .measurement_time(Duration::from_millis(2000)) | 
|  | .bench_function("test_meas_time_2", move |b| b.iter(|| clone.count())); | 
|  |  | 
|  | assert!(counter1.read() < counter2.read()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_bench_function() { | 
|  | let dir = temp_dir(); | 
|  | short_benchmark(&dir).bench_function("test_bench_function", move |b| b.iter(|| 10)); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_filtering() { | 
|  | let dir = temp_dir(); | 
|  | let counter = Counter::default(); | 
|  | let clone = counter.clone(); | 
|  |  | 
|  | short_benchmark(&dir) | 
|  | .with_filter("Foo") | 
|  | .bench_function("test_filtering", move |b| b.iter(|| clone.count())); | 
|  |  | 
|  | assert_eq!(counter.read(), 0); | 
|  | assert!(!dir.path().join("test_filtering").is_dir()); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_timing_loops() { | 
|  | let dir = temp_dir(); | 
|  | let mut c = short_benchmark(&dir); | 
|  | let mut group = c.benchmark_group("test_timing_loops"); | 
|  | group.bench_function("iter_with_setup", |b| { | 
|  | b.iter_with_setup(|| vec![10], |v| v[0]) | 
|  | }); | 
|  | group.bench_function("iter_with_large_setup", |b| { | 
|  | b.iter_batched(|| vec![10], |v| v[0], BatchSize::NumBatches(1)) | 
|  | }); | 
|  | group.bench_function("iter_with_large_drop", |b| { | 
|  | b.iter_with_large_drop(|| vec![10; 100]) | 
|  | }); | 
|  | group.bench_function("iter_batched_small", |b| { | 
|  | b.iter_batched(|| vec![10], |v| v[0], BatchSize::SmallInput) | 
|  | }); | 
|  | group.bench_function("iter_batched_large", |b| { | 
|  | b.iter_batched(|| vec![10], |v| v[0], BatchSize::LargeInput) | 
|  | }); | 
|  | group.bench_function("iter_batched_per_iteration", |b| { | 
|  | b.iter_batched(|| vec![10], |v| v[0], BatchSize::PerIteration) | 
|  | }); | 
|  | group.bench_function("iter_batched_one_batch", |b| { | 
|  | b.iter_batched(|| vec![10], |v| v[0], BatchSize::NumBatches(1)) | 
|  | }); | 
|  | group.bench_function("iter_batched_10_iterations", |b| { | 
|  | b.iter_batched(|| vec![10], |v| v[0], BatchSize::NumIterations(10)) | 
|  | }); | 
|  | group.bench_function("iter_batched_ref_small", |b| { | 
|  | b.iter_batched_ref(|| vec![10], |v| v[0], BatchSize::SmallInput) | 
|  | }); | 
|  | group.bench_function("iter_batched_ref_large", |b| { | 
|  | b.iter_batched_ref(|| vec![10], |v| v[0], BatchSize::LargeInput) | 
|  | }); | 
|  | group.bench_function("iter_batched_ref_per_iteration", |b| { | 
|  | b.iter_batched_ref(|| vec![10], |v| v[0], BatchSize::PerIteration) | 
|  | }); | 
|  | group.bench_function("iter_batched_ref_one_batch", |b| { | 
|  | b.iter_batched_ref(|| vec![10], |v| v[0], BatchSize::NumBatches(1)) | 
|  | }); | 
|  | group.bench_function("iter_batched_ref_10_iterations", |b| { | 
|  | b.iter_batched_ref(|| vec![10], |v| v[0], BatchSize::NumIterations(10)) | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Verify that all expected output files are present | 
|  | #[cfg(feature = "plotters")] | 
|  | #[test] | 
|  | fn test_output_files() { | 
|  | let tempdir = temp_dir(); | 
|  | // Run benchmarks twice to produce comparisons | 
|  | for _ in 0..2 { | 
|  | let mut c = short_benchmark(&tempdir); | 
|  | let mut group = c.benchmark_group("test_output"); | 
|  | group.sampling_mode(SamplingMode::Linear); | 
|  | group.bench_function("output_1", |b| b.iter(|| 10)); | 
|  | group.bench_function("output_2", |b| b.iter(|| 20)); | 
|  | group.bench_function("output_\\/*\"?", |b| b.iter(|| 30)); | 
|  | } | 
|  |  | 
|  | // For each benchmark, assert that the expected files are present. | 
|  | for x in 0..3 { | 
|  | let dir = if x == 2 { | 
|  | // Check that certain special characters are replaced with underscores | 
|  | tempdir.path().join("test_output/output______") | 
|  | } else { | 
|  | tempdir.path().join(format!("test_output/output_{}", x + 1)) | 
|  | }; | 
|  |  | 
|  | verify_stats(&dir, "new"); | 
|  | verify_stats(&dir, "base"); | 
|  | verify_json(&dir, "change/estimates.json"); | 
|  |  | 
|  | #[cfg(feature = "html_reports")] | 
|  | { | 
|  | verify_svg(&dir, "report/MAD.svg"); | 
|  | verify_svg(&dir, "report/mean.svg"); | 
|  | verify_svg(&dir, "report/median.svg"); | 
|  | verify_svg(&dir, "report/pdf.svg"); | 
|  | verify_svg(&dir, "report/regression.svg"); | 
|  | verify_svg(&dir, "report/SD.svg"); | 
|  | verify_svg(&dir, "report/slope.svg"); | 
|  | verify_svg(&dir, "report/typical.svg"); | 
|  | verify_svg(&dir, "report/both/pdf.svg"); | 
|  | verify_svg(&dir, "report/both/regression.svg"); | 
|  | verify_svg(&dir, "report/change/mean.svg"); | 
|  | verify_svg(&dir, "report/change/median.svg"); | 
|  | verify_svg(&dir, "report/change/t-test.svg"); | 
|  |  | 
|  | verify_svg(&dir, "report/pdf_small.svg"); | 
|  | verify_svg(&dir, "report/regression_small.svg"); | 
|  | verify_svg(&dir, "report/relative_pdf_small.svg"); | 
|  | verify_svg(&dir, "report/relative_regression_small.svg"); | 
|  | verify_html(&dir, "report/index.html"); | 
|  | } | 
|  | } | 
|  |  | 
|  | #[cfg(feature = "html_reports")] | 
|  | { | 
|  | // Check for overall report files | 
|  | let dir = tempdir.path().join("test_output"); | 
|  |  | 
|  | verify_svg(&dir, "report/violin.svg"); | 
|  | verify_html(&dir, "report/index.html"); | 
|  | } | 
|  |  | 
|  | // Run the final summary process and check for the report that produces | 
|  | short_benchmark(&tempdir).final_summary(); | 
|  |  | 
|  | #[cfg(feature = "html_reports")] | 
|  | { | 
|  | let dir = tempdir.path().to_owned(); | 
|  | verify_html(&dir, "report/index.html"); | 
|  | } | 
|  | } | 
|  |  | 
|  | #[cfg(feature = "plotters")] | 
|  | #[test] | 
|  | fn test_output_files_flat_sampling() { | 
|  | let tempdir = temp_dir(); | 
|  | // Run benchmark twice to produce comparisons | 
|  | for _ in 0..2 { | 
|  | let mut c = short_benchmark(&tempdir); | 
|  | let mut group = c.benchmark_group("test_output"); | 
|  | group.sampling_mode(SamplingMode::Flat); | 
|  | group.bench_function("output_flat", |b| b.iter(|| 10)); | 
|  | } | 
|  |  | 
|  | let dir = tempdir.path().join("test_output/output_flat"); | 
|  |  | 
|  | verify_stats(&dir, "new"); | 
|  | verify_stats(&dir, "base"); | 
|  | verify_json(&dir, "change/estimates.json"); | 
|  |  | 
|  | #[cfg(feature = "html_reports")] | 
|  | { | 
|  | verify_svg(&dir, "report/MAD.svg"); | 
|  | verify_svg(&dir, "report/mean.svg"); | 
|  | verify_svg(&dir, "report/median.svg"); | 
|  | verify_svg(&dir, "report/pdf.svg"); | 
|  | verify_svg(&dir, "report/iteration_times.svg"); | 
|  | verify_svg(&dir, "report/SD.svg"); | 
|  | verify_svg(&dir, "report/typical.svg"); | 
|  | verify_svg(&dir, "report/both/pdf.svg"); | 
|  | verify_svg(&dir, "report/both/iteration_times.svg"); | 
|  | verify_svg(&dir, "report/change/mean.svg"); | 
|  | verify_svg(&dir, "report/change/median.svg"); | 
|  | verify_svg(&dir, "report/change/t-test.svg"); | 
|  |  | 
|  | verify_svg(&dir, "report/pdf_small.svg"); | 
|  | verify_svg(&dir, "report/iteration_times_small.svg"); | 
|  | verify_svg(&dir, "report/relative_pdf_small.svg"); | 
|  | verify_svg(&dir, "report/relative_iteration_times_small.svg"); | 
|  | verify_html(&dir, "report/index.html"); | 
|  | } | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | #[should_panic(expected = "Benchmark function must call Bencher::iter or related method.")] | 
|  | fn test_bench_with_no_iteration_panics() { | 
|  | let dir = temp_dir(); | 
|  | short_benchmark(&dir).bench_function("no_iter", |_b| {}); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_benchmark_group_with_input() { | 
|  | let dir = temp_dir(); | 
|  | let mut c = short_benchmark(&dir); | 
|  | let mut group = c.benchmark_group("Test Group"); | 
|  | for x in 0..2 { | 
|  | group.bench_with_input(BenchmarkId::new("Test 1", x), &x, |b, i| b.iter(|| i)); | 
|  | group.bench_with_input(BenchmarkId::new("Test 2", x), &x, |b, i| b.iter(|| i)); | 
|  | } | 
|  | group.finish(); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_benchmark_group_without_input() { | 
|  | let dir = temp_dir(); | 
|  | let mut c = short_benchmark(&dir); | 
|  | let mut group = c.benchmark_group("Test Group 2"); | 
|  | group.bench_function("Test 1", |b| b.iter(|| 30)); | 
|  | group.bench_function("Test 2", |b| b.iter(|| 20)); | 
|  | group.finish(); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn test_criterion_doesnt_panic_if_measured_time_is_zero() { | 
|  | let dir = temp_dir(); | 
|  | let mut c = short_benchmark(&dir); | 
|  | c.bench_function("zero_time", |bencher| { | 
|  | bencher.iter_custom(|_iters| Duration::new(0, 0)) | 
|  | }); | 
|  | } | 
|  |  | 
|  | mod macros { | 
|  | use super::{criterion, criterion_group, criterion_main}; | 
|  |  | 
|  | #[test] | 
|  | #[should_panic(expected = "group executed")] | 
|  | fn criterion_main() { | 
|  | fn group() {} | 
|  | fn group2() { | 
|  | panic!("group executed"); | 
|  | } | 
|  |  | 
|  | criterion_main!(group, group2); | 
|  |  | 
|  | main(); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn criterion_main_trailing_comma() { | 
|  | // make this a compile-only check | 
|  | // as the second logger initialization causes panic | 
|  | #[allow(dead_code)] | 
|  | fn group() {} | 
|  | #[allow(dead_code)] | 
|  | fn group2() {} | 
|  |  | 
|  | criterion_main!(group, group2,); | 
|  |  | 
|  | // silence dead_code warning | 
|  | if false { | 
|  | main() | 
|  | } | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | #[should_panic(expected = "group executed")] | 
|  | fn criterion_group() { | 
|  | use self::criterion::Criterion; | 
|  |  | 
|  | fn group(_crit: &mut Criterion) {} | 
|  | fn group2(_crit: &mut Criterion) { | 
|  | panic!("group executed"); | 
|  | } | 
|  |  | 
|  | criterion_group!(test_group, group, group2); | 
|  |  | 
|  | test_group(); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | #[should_panic(expected = "group executed")] | 
|  | fn criterion_group_trailing_comma() { | 
|  | use self::criterion::Criterion; | 
|  |  | 
|  | fn group(_crit: &mut Criterion) {} | 
|  | fn group2(_crit: &mut Criterion) { | 
|  | panic!("group executed"); | 
|  | } | 
|  |  | 
|  | criterion_group!(test_group, group, group2,); | 
|  |  | 
|  | test_group(); | 
|  | } | 
|  | } | 
|  |  | 
|  | struct TestProfiler { | 
|  | started: Rc<Cell<u32>>, | 
|  | stopped: Rc<Cell<u32>>, | 
|  | } | 
|  | impl Profiler for TestProfiler { | 
|  | fn start_profiling(&mut self, benchmark_id: &str, _benchmark_path: &Path) { | 
|  | assert!(benchmark_id.contains("profile_test")); | 
|  | self.started.set(self.started.get() + 1); | 
|  | } | 
|  | fn stop_profiling(&mut self, benchmark_id: &str, _benchmark_path: &Path) { | 
|  | assert!(benchmark_id.contains("profile_test")); | 
|  | self.stopped.set(self.stopped.get() + 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Verify that profilers are started and stopped as expected | 
|  | #[test] | 
|  | fn test_profiler_called() { | 
|  | let started = Rc::new(Cell::new(0u32)); | 
|  | let stopped = Rc::new(Cell::new(0u32)); | 
|  | let profiler = TestProfiler { | 
|  | started: started.clone(), | 
|  | stopped: stopped.clone(), | 
|  | }; | 
|  | let dir = temp_dir(); | 
|  | let mut criterion = short_benchmark(&dir) | 
|  | .with_profiler(profiler) | 
|  | .profile_time(Some(Duration::from_secs(1))); | 
|  | criterion.bench_function("profile_test", |b| b.iter(|| 10)); | 
|  | assert_eq!(1, started.get()); | 
|  | assert_eq!(1, stopped.get()); | 
|  | } |