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