1use perfgate_types::{BenchConfigFile, ConfigFile, DefaultsConfig};
7use std::fmt;
8use std::path::{Path, PathBuf};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum BenchSource {
17 CargoTarget,
19 Criterion,
21 GoBench,
23 PytestBenchmark,
25 Custom,
27}
28
29impl fmt::Display for BenchSource {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 BenchSource::CargoTarget => write!(f, "cargo bench target"),
33 BenchSource::Criterion => write!(f, "criterion benchmark"),
34 BenchSource::GoBench => write!(f, "go benchmark"),
35 BenchSource::PytestBenchmark => write!(f, "pytest-benchmark"),
36 BenchSource::Custom => write!(f, "custom"),
37 }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct DiscoveredBench {
44 pub name: String,
45 pub command: Vec<String>,
46 pub source: BenchSource,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum Preset {
52 Standard,
53 Release,
54 Tier1Fast,
55}
56
57impl Preset {
58 pub fn defaults(self) -> DefaultsConfig {
59 match self {
60 Preset::Standard => DefaultsConfig {
61 repeat: Some(5),
62 warmup: Some(1),
63 threshold: Some(0.20),
64 ..DefaultsConfig::default()
65 },
66 Preset::Release => DefaultsConfig {
67 repeat: Some(10),
68 warmup: Some(2),
69 threshold: Some(0.10),
70 ..DefaultsConfig::default()
71 },
72 Preset::Tier1Fast => DefaultsConfig {
73 repeat: Some(3),
74 warmup: Some(1),
75 threshold: Some(0.30),
76 ..DefaultsConfig::default()
77 },
78 }
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum CiPlatform {
85 GitHub,
86 GitLab,
87 Bitbucket,
88 CircleCi,
89}
90
91pub fn discover_benchmarks(root: &Path) -> Vec<DiscoveredBench> {
97 let mut found: Vec<DiscoveredBench> = Vec::new();
98
99 discover_rust_benches(root, &mut found);
100 discover_go_benches(root, &mut found);
101 discover_python_benches(root, &mut found);
102
103 let mut seen = std::collections::HashSet::new();
105 found.retain(|b| seen.insert(b.name.clone()));
106
107 found
108}
109
110fn discover_rust_benches(root: &Path, out: &mut Vec<DiscoveredBench>) {
113 let cargo_toml = root.join("Cargo.toml");
114 if !cargo_toml.is_file() {
115 return;
116 }
117
118 let content = match std::fs::read_to_string(&cargo_toml) {
119 Ok(c) => c,
120 Err(_) => return,
121 };
122
123 if let Ok(parsed) = content.parse::<toml::Table>()
125 && let Some(toml::Value::Array(benches)) = parsed.get("bench")
126 {
127 for bench in benches {
128 if let Some(name) = bench.get("name").and_then(|v| v.as_str()) {
129 let harness = bench
130 .get("harness")
131 .and_then(|v| v.as_bool())
132 .unwrap_or(true);
133
134 let source = if harness {
135 BenchSource::CargoTarget
136 } else {
137 BenchSource::Criterion
139 };
140
141 out.push(DiscoveredBench {
142 name: name.to_string(),
143 command: vec![
144 "cargo".into(),
145 "bench".into(),
146 "--bench".into(),
147 name.to_string(),
148 ],
149 source,
150 });
151 }
152 }
153 }
154
155 let benches_dir = root.join("benches");
157 if benches_dir.is_dir() {
158 scan_dir_for_criterion(&benches_dir, out);
159 }
160}
161
162fn scan_dir_for_criterion(dir: &Path, out: &mut Vec<DiscoveredBench>) {
163 let entries = match std::fs::read_dir(dir) {
164 Ok(e) => e,
165 Err(_) => return,
166 };
167
168 for entry in entries.flatten() {
169 let path = entry.path();
170 if path.extension().and_then(|e| e.to_str()) != Some("rs") {
171 continue;
172 }
173
174 let content = match std::fs::read_to_string(&path) {
175 Ok(c) => c,
176 Err(_) => continue,
177 };
178
179 if content.contains("criterion_group!") || content.contains("criterion_main!") {
180 let stem = path
181 .file_stem()
182 .and_then(|s| s.to_str())
183 .unwrap_or("benchmark");
184
185 if !out.iter().any(|b| b.name == stem) {
187 out.push(DiscoveredBench {
188 name: stem.to_string(),
189 command: vec![
190 "cargo".into(),
191 "bench".into(),
192 "--bench".into(),
193 stem.to_string(),
194 ],
195 source: BenchSource::Criterion,
196 });
197 }
198 }
199 }
200}
201
202fn discover_go_benches(root: &Path, out: &mut Vec<DiscoveredBench>) {
205 if !root.join("go.mod").is_file() {
207 return;
208 }
209
210 walk_for_go_bench_files(root, root, out);
211}
212
213fn walk_for_go_bench_files(root: &Path, dir: &Path, out: &mut Vec<DiscoveredBench>) {
214 let entries = match std::fs::read_dir(dir) {
215 Ok(e) => e,
216 Err(_) => return,
217 };
218
219 for entry in entries.flatten() {
220 let path = entry.path();
221
222 if path.is_dir() {
223 let name = path
224 .file_name()
225 .and_then(|n| n.to_str())
226 .unwrap_or_default();
227 if name.starts_with('.') || name == "vendor" || name == "node_modules" {
228 continue;
229 }
230 walk_for_go_bench_files(root, &path, out);
231 continue;
232 }
233
234 let file_name = path
235 .file_name()
236 .and_then(|n| n.to_str())
237 .unwrap_or_default();
238 if !file_name.ends_with("_test.go") {
239 continue;
240 }
241
242 let content = match std::fs::read_to_string(&path) {
243 Ok(c) => c,
244 Err(_) => continue,
245 };
246
247 if content.contains("func Benchmark") {
248 let pkg_dir = path.parent().unwrap_or(root);
249 let rel = pkg_dir
250 .strip_prefix(root)
251 .unwrap_or(pkg_dir)
252 .to_string_lossy()
253 .replace('\\', "/");
254
255 let pkg = if rel.is_empty() {
256 ".".to_string()
257 } else {
258 format!("./{rel}")
259 };
260
261 let bench_name = format!("go-bench-{}", rel.replace('/', "-")).replace("..", "root");
262 let bench_name = if bench_name == "go-bench-" {
263 "go-bench".to_string()
264 } else {
265 bench_name
266 };
267
268 if !out.iter().any(|b| b.name == bench_name) {
269 out.push(DiscoveredBench {
270 name: bench_name,
271 command: vec![
272 "go".into(),
273 "test".into(),
274 "-bench=.".into(),
275 "-benchmem".into(),
276 pkg,
277 ],
278 source: BenchSource::GoBench,
279 });
280 }
281 }
282 }
283}
284
285fn discover_python_benches(root: &Path, out: &mut Vec<DiscoveredBench>) {
288 let markers = [
289 "requirements.txt",
290 "requirements-dev.txt",
291 "requirements-test.txt",
292 "setup.py",
293 "setup.cfg",
294 "pyproject.toml",
295 ];
296
297 let mut has_pytest_benchmark = false;
298 for marker in &markers {
299 let path = root.join(marker);
300 if let Ok(content) = std::fs::read_to_string(&path)
301 && (content.contains("pytest-benchmark") || content.contains("pytest_benchmark"))
302 {
303 has_pytest_benchmark = true;
304 break;
305 }
306 }
307
308 if !has_pytest_benchmark {
310 let conftest = root.join("conftest.py");
311 if let Ok(content) = std::fs::read_to_string(&conftest)
312 && content.contains("benchmark")
313 {
314 has_pytest_benchmark = true;
315 }
316 }
317
318 if has_pytest_benchmark {
319 out.push(DiscoveredBench {
320 name: "pytest-bench".to_string(),
321 command: vec![
322 "pytest".into(),
323 "--benchmark-only".into(),
324 "--benchmark-json=benchmark.json".into(),
325 ],
326 source: BenchSource::PytestBenchmark,
327 });
328 }
329}
330
331pub fn generate_config(benchmarks: &[DiscoveredBench], preset: Preset) -> ConfigFile {
337 let defaults = preset.defaults();
338
339 let benches: Vec<BenchConfigFile> = benchmarks
340 .iter()
341 .map(|b| BenchConfigFile {
342 name: b.name.clone(),
343 command: b.command.clone(),
344 cwd: None,
345 work: None,
346 timeout: None,
347 repeat: None,
348 warmup: None,
349 metrics: None,
350 budgets: None,
351 scaling: None,
352 })
353 .collect();
354
355 ConfigFile {
356 defaults,
357 benches,
358 ..ConfigFile::default()
359 }
360}
361
362pub fn render_config_toml(config: &ConfigFile) -> String {
364 let mut out = String::new();
365
366 out.push_str("# perfgate.toml — generated by `perfgate init`\n");
367 out.push_str("#\n");
368 out.push_str("# Documentation: https://github.com/EffortlessMetrics/perfgate\n\n");
369
370 out.push_str("# Default settings applied to all benchmarks unless overridden.\n");
372 out.push_str("[defaults]\n");
373 if let Some(repeat) = config.defaults.repeat {
374 out.push_str(&format!(
375 "# Number of measured samples per benchmark run.\n\
376 repeat = {repeat}\n"
377 ));
378 }
379 if let Some(warmup) = config.defaults.warmup {
380 out.push_str(&format!(
381 "# Warmup iterations excluded from statistics.\n\
382 warmup = {warmup}\n"
383 ));
384 }
385 if let Some(threshold) = config.defaults.threshold {
386 out.push_str(&format!(
387 "# Maximum allowed regression fraction (0.20 = 20%).\n\
388 threshold = {threshold:.2}\n"
389 ));
390 }
391 if let Some(ref out_dir) = config.defaults.out_dir {
392 out.push_str(&format!("out_dir = \"{out_dir}\"\n"));
393 }
394 if let Some(ref baseline_dir) = config.defaults.baseline_dir {
395 out.push_str(&format!("baseline_dir = \"{baseline_dir}\"\n"));
396 }
397
398 for bench in &config.benches {
400 out.push_str(&format!("\n[[bench]]\nname = \"{}\"\n", bench.name));
401
402 let parts: Vec<String> = bench.command.iter().map(|c| format!("\"{c}\"")).collect();
404 out.push_str(&format!("command = [{}]\n", parts.join(", ")));
405
406 if let Some(ref cwd) = bench.cwd {
407 out.push_str(&format!("cwd = \"{cwd}\"\n"));
408 }
409 if let Some(repeat) = bench.repeat {
410 out.push_str(&format!("repeat = {repeat}\n"));
411 }
412 if let Some(warmup) = bench.warmup {
413 out.push_str(&format!("warmup = {warmup}\n"));
414 }
415 if let Some(ref timeout) = bench.timeout {
416 out.push_str(&format!("timeout = \"{timeout}\"\n"));
417 }
418 }
419
420 out
421}
422
423pub fn scaffold_ci(platform: CiPlatform, config_path: &Path) -> String {
429 let config_str = config_path.to_string_lossy().replace('\\', "/");
430 match platform {
431 CiPlatform::GitHub => scaffold_github(&config_str),
432 CiPlatform::GitLab => scaffold_gitlab(&config_str),
433 CiPlatform::Bitbucket => scaffold_bitbucket(&config_str),
434 CiPlatform::CircleCi => scaffold_circleci(&config_str),
435 }
436}
437
438fn scaffold_github(config_path: &str) -> String {
439 format!(
440 r#"# .github/workflows/perfgate.yml — generated by `perfgate init`
441name: Performance Gate
442
443on:
444 pull_request:
445 branches: [main]
446
447permissions:
448 pull-requests: write
449
450jobs:
451 bench:
452 runs-on: ubuntu-latest
453 steps:
454 - uses: actions/checkout@v4
455
456 - name: Install perfgate
457 run: cargo install perfgate-cli
458
459 - name: Run benchmarks
460 run: perfgate check --config {config_path} --all --mode cockpit --out-dir artifacts/perfgate
461
462 - name: Upload artifacts
463 uses: actions/upload-artifact@v4
464 with:
465 name: perfgate
466 path: artifacts/perfgate/
467
468 - name: Post PR comment
469 if: github.event_name == 'pull_request'
470 run: |
471 if [ -f artifacts/perfgate/comment.md ]; then
472 gh pr comment ${{{{ github.event.pull_request.number }}}} --body-file artifacts/perfgate/comment.md
473 fi
474 env:
475 GH_TOKEN: ${{{{ secrets.GITHUB_TOKEN }}}}
476"#
477 )
478}
479
480fn scaffold_gitlab(config_path: &str) -> String {
481 format!(
482 r#"# .gitlab-ci.yml snippet — generated by `perfgate init`
483perfgate:
484 stage: test
485 script:
486 - cargo install perfgate-cli
487 - perfgate check --config {config_path} --all --mode cockpit --out-dir artifacts/perfgate
488 artifacts:
489 paths:
490 - artifacts/perfgate/
491 when: always
492"#
493 )
494}
495
496fn scaffold_bitbucket(config_path: &str) -> String {
497 format!(
498 r#"# bitbucket-pipelines.yml snippet — generated by `perfgate init`
499pipelines:
500 pull-requests:
501 '**':
502 - step:
503 name: Performance Gate
504 script:
505 - cargo install perfgate-cli
506 - perfgate check --config {config_path} --all --mode cockpit --out-dir artifacts/perfgate
507 artifacts:
508 - artifacts/perfgate/**
509"#
510 )
511}
512
513fn scaffold_circleci(config_path: &str) -> String {
514 format!(
515 r#"# .circleci/config.yml snippet — generated by `perfgate init`
516version: 2.1
517jobs:
518 perfgate:
519 docker:
520 - image: cimg/rust:1.80
521 steps:
522 - checkout
523 - run:
524 name: Install perfgate
525 command: cargo install perfgate-cli
526 - run:
527 name: Run benchmarks
528 command: perfgate check --config {config_path} --all --mode cockpit --out-dir artifacts/perfgate
529 - store_artifacts:
530 path: artifacts/perfgate
531"#
532 )
533}
534
535pub fn ci_workflow_path(platform: CiPlatform) -> PathBuf {
537 match platform {
538 CiPlatform::GitHub => PathBuf::from(".github/workflows/perfgate.yml"),
539 CiPlatform::GitLab => PathBuf::from(".gitlab-ci.perfgate.yml"),
540 CiPlatform::Bitbucket => PathBuf::from("bitbucket-pipelines.perfgate.yml"),
541 CiPlatform::CircleCi => PathBuf::from(".circleci/perfgate.yml"),
542 }
543}
544
545#[cfg(test)]
550mod tests {
551 use super::*;
552 use std::fs;
553
554 #[test]
557 fn preset_standard_defaults() {
558 let d = Preset::Standard.defaults();
559 assert_eq!(d.repeat, Some(5));
560 assert_eq!(d.warmup, Some(1));
561 assert_eq!(d.threshold, Some(0.20));
562 }
563
564 #[test]
565 fn preset_release_defaults() {
566 let d = Preset::Release.defaults();
567 assert_eq!(d.repeat, Some(10));
568 assert_eq!(d.warmup, Some(2));
569 assert_eq!(d.threshold, Some(0.10));
570 }
571
572 #[test]
573 fn preset_tier1fast_defaults() {
574 let d = Preset::Tier1Fast.defaults();
575 assert_eq!(d.repeat, Some(3));
576 assert_eq!(d.warmup, Some(1));
577 assert_eq!(d.threshold, Some(0.30));
578 }
579
580 #[test]
583 fn discover_cargo_bench_targets() {
584 let dir = tempfile::tempdir().unwrap();
585 let cargo = dir.path().join("Cargo.toml");
586 fs::write(
587 &cargo,
588 r#"
589[package]
590name = "example"
591version = "0.1.0"
592edition = "2021"
593
594[[bench]]
595name = "my-bench"
596harness = false
597"#,
598 )
599 .unwrap();
600
601 let found = discover_benchmarks(dir.path());
602 assert_eq!(found.len(), 1);
603 assert_eq!(found[0].name, "my-bench");
604 assert_eq!(found[0].source, BenchSource::Criterion); assert_eq!(
606 found[0].command,
607 vec!["cargo", "bench", "--bench", "my-bench"]
608 );
609 }
610
611 #[test]
612 fn discover_cargo_bench_harness_true() {
613 let dir = tempfile::tempdir().unwrap();
614 let cargo = dir.path().join("Cargo.toml");
615 fs::write(
616 &cargo,
617 r#"
618[package]
619name = "example"
620version = "0.1.0"
621edition = "2021"
622
623[[bench]]
624name = "basic"
625"#,
626 )
627 .unwrap();
628
629 let found = discover_benchmarks(dir.path());
630 assert_eq!(found.len(), 1);
631 assert_eq!(found[0].source, BenchSource::CargoTarget);
632 }
633
634 #[test]
635 fn discover_criterion_from_benches_dir() {
636 let dir = tempfile::tempdir().unwrap();
637 fs::write(
639 dir.path().join("Cargo.toml"),
640 "[package]\nname = \"x\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
641 )
642 .unwrap();
643
644 let benches_dir = dir.path().join("benches");
645 fs::create_dir(&benches_dir).unwrap();
646 fs::write(
647 benches_dir.join("perf.rs"),
648 "criterion_group!(benches, bench_fn);\ncriterion_main!(benches);\n",
649 )
650 .unwrap();
651
652 let found = discover_benchmarks(dir.path());
653 assert_eq!(found.len(), 1);
654 assert_eq!(found[0].name, "perf");
655 assert_eq!(found[0].source, BenchSource::Criterion);
656 }
657
658 #[test]
659 fn criterion_dedup_with_cargo_target() {
660 let dir = tempfile::tempdir().unwrap();
661 fs::write(
662 dir.path().join("Cargo.toml"),
663 r#"
664[package]
665name = "x"
666version = "0.1.0"
667edition = "2021"
668
669[[bench]]
670name = "perf"
671harness = false
672"#,
673 )
674 .unwrap();
675
676 let benches_dir = dir.path().join("benches");
677 fs::create_dir(&benches_dir).unwrap();
678 fs::write(
679 benches_dir.join("perf.rs"),
680 "criterion_group!(benches, bench_fn);\ncriterion_main!(benches);\n",
681 )
682 .unwrap();
683
684 let found = discover_benchmarks(dir.path());
685 assert_eq!(found.len(), 1);
687 assert_eq!(found[0].name, "perf");
688 }
689
690 #[test]
693 fn discover_go_benches() {
694 let dir = tempfile::tempdir().unwrap();
695 fs::write(dir.path().join("go.mod"), "module example\n").unwrap();
696 fs::write(
697 dir.path().join("bench_test.go"),
698 "package main\n\nfunc BenchmarkFoo(b *testing.B) {\n}\n",
699 )
700 .unwrap();
701
702 let found = discover_benchmarks(dir.path());
703 assert_eq!(found.len(), 1);
704 assert_eq!(found[0].name, "go-bench");
705 assert_eq!(found[0].source, BenchSource::GoBench);
706 assert!(found[0].command.contains(&"-bench=.".to_string()));
707 }
708
709 #[test]
710 fn discover_go_benches_in_subpackage() {
711 let dir = tempfile::tempdir().unwrap();
712 fs::write(dir.path().join("go.mod"), "module example\n").unwrap();
713 let sub = dir.path().join("pkg").join("fast");
714 fs::create_dir_all(&sub).unwrap();
715 fs::write(
716 sub.join("bench_test.go"),
717 "package fast\nfunc BenchmarkBar(b *testing.B) {}\n",
718 )
719 .unwrap();
720
721 let found = discover_benchmarks(dir.path());
722 assert_eq!(found.len(), 1);
723 assert_eq!(found[0].name, "go-bench-pkg-fast");
724 }
725
726 #[test]
729 fn discover_pytest_benchmark_from_requirements() {
730 let dir = tempfile::tempdir().unwrap();
731 fs::write(
732 dir.path().join("requirements.txt"),
733 "pytest\npytest-benchmark\n",
734 )
735 .unwrap();
736
737 let found = discover_benchmarks(dir.path());
738 assert_eq!(found.len(), 1);
739 assert_eq!(found[0].name, "pytest-bench");
740 assert_eq!(found[0].source, BenchSource::PytestBenchmark);
741 }
742
743 #[test]
744 fn discover_pytest_benchmark_from_pyproject() {
745 let dir = tempfile::tempdir().unwrap();
746 fs::write(
747 dir.path().join("pyproject.toml"),
748 "[project.optional-dependencies]\ntest = [\"pytest-benchmark\"]\n",
749 )
750 .unwrap();
751
752 let found = discover_benchmarks(dir.path());
753 assert_eq!(found.len(), 1);
754 assert_eq!(found[0].source, BenchSource::PytestBenchmark);
755 }
756
757 #[test]
758 fn discover_pytest_benchmark_from_conftest() {
759 let dir = tempfile::tempdir().unwrap();
760 fs::write(
761 dir.path().join("conftest.py"),
762 "def test_speed(benchmark):\n benchmark(lambda: None)\n",
763 )
764 .unwrap();
765
766 let found = discover_benchmarks(dir.path());
767 assert_eq!(found.len(), 1);
768 }
769
770 #[test]
773 fn empty_repo_discovers_nothing() {
774 let dir = tempfile::tempdir().unwrap();
775 let found = discover_benchmarks(dir.path());
776 assert!(found.is_empty());
777 }
778
779 #[test]
782 fn generate_config_produces_valid_toml() {
783 let benches = vec![
784 DiscoveredBench {
785 name: "my-bench".into(),
786 command: vec!["cargo".into(), "bench".into()],
787 source: BenchSource::CargoTarget,
788 },
789 DiscoveredBench {
790 name: "go-bench".into(),
791 command: vec!["go".into(), "test".into(), "-bench=.".into(), ".".into()],
792 source: BenchSource::GoBench,
793 },
794 ];
795
796 let config = generate_config(&benches, Preset::Standard);
797 assert_eq!(config.benches.len(), 2);
798 assert_eq!(config.defaults.repeat, Some(5));
799 assert_eq!(config.defaults.threshold, Some(0.20));
800 }
801
802 #[test]
803 fn render_config_toml_roundtrip() {
804 let benches = vec![DiscoveredBench {
805 name: "my-bench".into(),
806 command: vec![
807 "cargo".into(),
808 "bench".into(),
809 "--bench".into(),
810 "my-bench".into(),
811 ],
812 source: BenchSource::CargoTarget,
813 }];
814
815 let config = generate_config(&benches, Preset::Release);
816 let toml_str = render_config_toml(&config);
817
818 let parsed: ConfigFile = toml::from_str(&toml_str).expect("rendered TOML should parse");
820 assert_eq!(parsed.benches.len(), 1);
821 assert_eq!(parsed.benches[0].name, "my-bench");
822 assert_eq!(parsed.defaults.repeat, Some(10));
823 assert_eq!(parsed.defaults.threshold, Some(0.10));
824 }
825
826 #[test]
829 fn scaffold_github_ci() {
830 let content = scaffold_ci(CiPlatform::GitHub, Path::new("perfgate.toml"));
831 assert!(content.contains("perfgate check"));
832 assert!(content.contains("perfgate.toml"));
833 assert!(content.contains("ubuntu-latest"));
834 }
835
836 #[test]
837 fn scaffold_gitlab_ci() {
838 let content = scaffold_ci(CiPlatform::GitLab, Path::new("perfgate.toml"));
839 assert!(content.contains("perfgate check"));
840 assert!(content.contains("stage: test"));
841 }
842
843 #[test]
844 fn scaffold_bitbucket_ci() {
845 let content = scaffold_ci(CiPlatform::Bitbucket, Path::new("perfgate.toml"));
846 assert!(content.contains("perfgate check"));
847 assert!(content.contains("pipelines"));
848 }
849
850 #[test]
851 fn scaffold_circleci_ci() {
852 let content = scaffold_ci(CiPlatform::CircleCi, Path::new("perfgate.toml"));
853 assert!(content.contains("perfgate check"));
854 assert!(content.contains("version: 2.1"));
855 }
856
857 #[test]
858 fn ci_workflow_paths() {
859 assert_eq!(
860 ci_workflow_path(CiPlatform::GitHub),
861 PathBuf::from(".github/workflows/perfgate.yml")
862 );
863 assert_eq!(
864 ci_workflow_path(CiPlatform::GitLab),
865 PathBuf::from(".gitlab-ci.perfgate.yml")
866 );
867 }
868
869 #[test]
872 fn bench_source_display() {
873 assert_eq!(
874 format!("{}", BenchSource::CargoTarget),
875 "cargo bench target"
876 );
877 assert_eq!(format!("{}", BenchSource::Criterion), "criterion benchmark");
878 assert_eq!(format!("{}", BenchSource::GoBench), "go benchmark");
879 assert_eq!(
880 format!("{}", BenchSource::PytestBenchmark),
881 "pytest-benchmark"
882 );
883 assert_eq!(format!("{}", BenchSource::Custom), "custom");
884 }
885
886 #[test]
889 fn discover_mixed_repo() {
890 let dir = tempfile::tempdir().unwrap();
891
892 fs::write(
894 dir.path().join("Cargo.toml"),
895 r#"
896[package]
897name = "mixed"
898version = "0.1.0"
899edition = "2021"
900
901[[bench]]
902name = "rust-bench"
903harness = false
904"#,
905 )
906 .unwrap();
907
908 fs::write(dir.path().join("go.mod"), "module mixed\n").unwrap();
910 fs::write(
911 dir.path().join("bench_test.go"),
912 "package main\nfunc BenchmarkX(b *testing.B) {}\n",
913 )
914 .unwrap();
915
916 fs::write(dir.path().join("requirements.txt"), "pytest-benchmark\n").unwrap();
918
919 let found = discover_benchmarks(dir.path());
920 assert_eq!(found.len(), 3);
921
922 let names: Vec<&str> = found.iter().map(|b| b.name.as_str()).collect();
923 assert!(names.contains(&"rust-bench"));
924 assert!(names.contains(&"go-bench"));
925 assert!(names.contains(&"pytest-bench"));
926 }
927}