Skip to content

Commit 90677e3

Browse files
committed
wip(benchmarks): parser benches
1 parent 3a45607 commit 90677e3

File tree

2 files changed

+325
-5
lines changed

2 files changed

+325
-5
lines changed

src/benches/parser_bench.rs

Lines changed: 323 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,224 @@
1-
use codeinput::core::parser::parse_line;
2-
use criterion::{Criterion, black_box, criterion_group, criterion_main};
1+
use codeinput::core::parser::{parse_codeowners, parse_line, parse_owner};
2+
use criterion::{Criterion, criterion_group, criterion_main};
3+
use std::hint::black_box;
4+
use std::io::Write;
35
use std::path::Path;
6+
use tempfile::NamedTempFile;
47

8+
// parse_codeowners benchmark
9+
fn bench_parse_codeowners_small_file(c: &mut Criterion) {
10+
let mut temp_file = NamedTempFile::new().unwrap();
11+
writeln!(temp_file, "# Small CODEOWNERS file").unwrap();
12+
writeln!(temp_file, "*.js @frontend-team").unwrap();
13+
writeln!(temp_file, "*.rs @backend-team").unwrap();
14+
writeln!(temp_file, "/docs/ @docs-team #documentation").unwrap();
15+
writeln!(temp_file, "*.md @org/writers user@example.com #content").unwrap();
16+
temp_file.flush().unwrap();
17+
18+
c.bench_function("parse_codeowners_small", |b| {
19+
b.iter(|| parse_codeowners(black_box(temp_file.path())).unwrap())
20+
});
21+
}
22+
23+
fn bench_parse_codeowners_medium_file(c: &mut Criterion) {
24+
let mut temp_file = NamedTempFile::new().unwrap();
25+
writeln!(temp_file, "# Medium CODEOWNERS file with various patterns").unwrap();
26+
writeln!(temp_file).unwrap();
27+
writeln!(temp_file, "# Global owners").unwrap();
28+
writeln!(temp_file, "* @org/global-team").unwrap();
29+
writeln!(temp_file).unwrap();
30+
writeln!(temp_file, "# Frontend").unwrap();
31+
writeln!(temp_file, "*.js @frontend-team @alice #frontend").unwrap();
32+
writeln!(temp_file, "*.ts @frontend-team @bob #frontend #typescript").unwrap();
33+
writeln!(temp_file, "*.tsx @org/react-team user@react.com #ui #react").unwrap();
34+
writeln!(temp_file, "*.vue @org/vue-team #frontend #vue").unwrap();
35+
writeln!(temp_file, "/src/components/ @org/ui-team #components").unwrap();
36+
writeln!(temp_file, "/src/pages/ @org/frontend @charlie #pages").unwrap();
37+
writeln!(temp_file).unwrap();
38+
writeln!(temp_file, "# Backend").unwrap();
39+
writeln!(temp_file, "*.rs @backend-team @dave #backend #rust").unwrap();
40+
writeln!(
41+
temp_file,
42+
"*.go @org/go-team engineer@backend.com #backend #go"
43+
)
44+
.unwrap();
45+
writeln!(temp_file, "*.py @org/python-team #backend #python").unwrap();
46+
writeln!(temp_file, "/src/api/ @org/api-team #api").unwrap();
47+
writeln!(temp_file, "/src/database/ @org/db-team @eve #database").unwrap();
48+
writeln!(temp_file).unwrap();
49+
writeln!(temp_file, "# Infrastructure").unwrap();
50+
writeln!(temp_file, "Dockerfile @org/devops #docker #infrastructure").unwrap();
51+
writeln!(temp_file, "*.yml @org/devops @frank #ci #infrastructure").unwrap();
52+
writeln!(temp_file, "*.yaml @org/devops #ci #infrastructure").unwrap();
53+
writeln!(
54+
temp_file,
55+
"/terraform/ @org/infrastructure #terraform #infrastructure"
56+
)
57+
.unwrap();
58+
writeln!(temp_file, "/k8s/ @org/k8s-team ops@company.com #kubernetes").unwrap();
59+
writeln!(temp_file).unwrap();
60+
writeln!(temp_file, "# Documentation").unwrap();
61+
writeln!(temp_file, "*.md @docs-team @grace #documentation").unwrap();
62+
writeln!(temp_file, "/docs/ @org/tech-writers #documentation").unwrap();
63+
writeln!(temp_file, "README.md @org/maintainers #readme").unwrap();
64+
temp_file.flush().unwrap();
65+
66+
c.bench_function("parse_codeowners_medium", |b| {
67+
b.iter(|| parse_codeowners(black_box(temp_file.path())).unwrap())
68+
});
69+
}
70+
71+
fn bench_parse_codeowners_complex_file(c: &mut Criterion) {
72+
let mut temp_file = NamedTempFile::new().unwrap();
73+
writeln!(
74+
temp_file,
75+
"# Complex CODEOWNERS file with many patterns and edge cases"
76+
)
77+
.unwrap();
78+
writeln!(temp_file).unwrap();
79+
writeln!(temp_file, "# Global fallback").unwrap();
80+
writeln!(temp_file, "* @org/global-maintainers NOOWNER").unwrap();
81+
writeln!(temp_file).unwrap();
82+
writeln!(temp_file, "# Frontend microservices").unwrap();
83+
writeln!(temp_file, "/services/web-app/**/*.js @org/web-frontend @alice @bob user1@frontend.com #frontend #webapp").unwrap();
84+
writeln!(temp_file, "/services/web-app/**/*.ts @org/web-frontend @charlie engineer@webapp.com #frontend #typescript #webapp").unwrap();
85+
writeln!(temp_file, "/services/mobile-app/**/*.tsx @org/mobile-team @dave @eve mobile@company.com #mobile #react-native").unwrap();
86+
writeln!(
87+
temp_file,
88+
"/services/admin-portal/**/*.vue @org/admin-team admin@company.com #admin #vue #portal"
89+
)
90+
.unwrap();
91+
writeln!(temp_file).unwrap();
92+
writeln!(temp_file, "# Backend services").unwrap();
93+
writeln!(temp_file, "/services/user-service/**/*.rs @org/user-team @frank backend1@company.com #backend #rust #users").unwrap();
94+
writeln!(temp_file, "/services/payment-service/**/*.go @org/payments @grace @henry payments@company.com #backend #go #payments #critical").unwrap();
95+
writeln!(temp_file, "/services/notification-service/**/*.py @org/notifications notifications@company.com #backend #python #notifications").unwrap();
96+
writeln!(temp_file, "/services/analytics-service/**/*.scala @org/analytics @ivan analytics@company.com #backend #scala #analytics").unwrap();
97+
writeln!(temp_file).unwrap();
98+
writeln!(temp_file, "# Infrastructure and DevOps").unwrap();
99+
writeln!(temp_file, "/infrastructure/terraform/**/*.tf @org/infrastructure @jack @kate infra@company.com #terraform #infrastructure #critical").unwrap();
100+
writeln!(temp_file, "/infrastructure/k8s/**/*.yaml @org/k8s-team @liam k8s@company.com #kubernetes #infrastructure").unwrap();
101+
writeln!(temp_file, "/infrastructure/monitoring/**/*.yml @org/monitoring @mike monitoring@company.com #monitoring #infrastructure").unwrap();
102+
writeln!(
103+
temp_file,
104+
"/.github/workflows/**/*.yml @org/ci-cd @nina @oscar ci@company.com #ci #github-actions"
105+
)
106+
.unwrap();
107+
writeln!(temp_file).unwrap();
108+
writeln!(temp_file, "# Security and compliance").unwrap();
109+
writeln!(
110+
temp_file,
111+
"/security/**/* @org/security @paul @quinn security@company.com #security #critical"
112+
)
113+
.unwrap();
114+
writeln!(
115+
temp_file,
116+
"/**/*secret* @org/security security@company.com #security #secrets #critical"
117+
)
118+
.unwrap();
119+
writeln!(
120+
temp_file,
121+
"/**/*key* @org/security #security #secrets #critical"
122+
)
123+
.unwrap();
124+
writeln!(
125+
temp_file,
126+
"/compliance/**/*.json @org/compliance @rachel compliance@company.com #compliance #audit"
127+
)
128+
.unwrap();
129+
writeln!(temp_file).unwrap();
130+
writeln!(temp_file, "# Database and migrations").unwrap();
131+
writeln!(temp_file, "/database/migrations/**/*.sql @org/db-team @steve @tina db@company.com #database #migrations #critical").unwrap();
132+
writeln!(temp_file, "/database/schemas/**/*.sql @org/db-architects db-arch@company.com #database #schema #critical").unwrap();
133+
writeln!(temp_file).unwrap();
134+
writeln!(temp_file, "# Documentation and configuration").unwrap();
135+
writeln!(
136+
temp_file,
137+
"/docs/**/*.md @org/tech-writers @uma docs@company.com #documentation"
138+
)
139+
.unwrap();
140+
writeln!(
141+
temp_file,
142+
"/docs/api/**/*.md @org/api-docs @victor api-docs@company.com #documentation #api"
143+
)
144+
.unwrap();
145+
writeln!(
146+
temp_file,
147+
"/config/**/*.toml @org/config-team @walter config@company.com #configuration"
148+
)
149+
.unwrap();
150+
writeln!(
151+
temp_file,
152+
"/config/**/*.json @org/config-team config@company.com #configuration #json"
153+
)
154+
.unwrap();
155+
writeln!(temp_file).unwrap();
156+
writeln!(temp_file, "# Testing and quality").unwrap();
157+
writeln!(
158+
temp_file,
159+
"/tests/**/*.rs @org/qa-team @xander @yara qa@company.com #testing #rust"
160+
)
161+
.unwrap();
162+
writeln!(
163+
temp_file,
164+
"/tests/**/*.js @org/qa-frontend qa-frontend@company.com #testing #javascript"
165+
)
166+
.unwrap();
167+
writeln!(
168+
temp_file,
169+
"/benchmarks/**/* @org/performance @zoe perf@company.com #performance #benchmarks"
170+
)
171+
.unwrap();
172+
writeln!(temp_file).unwrap();
173+
writeln!(temp_file, "# Special files and patterns").unwrap();
174+
writeln!(
175+
temp_file,
176+
"Cargo.toml @org/rust-maintainers rust@company.com #rust #dependencies"
177+
)
178+
.unwrap();
179+
writeln!(
180+
temp_file,
181+
"package.json @org/js-maintainers js@company.com #javascript #dependencies"
182+
)
183+
.unwrap();
184+
writeln!(
185+
temp_file,
186+
"go.mod @org/go-maintainers go@company.com #go #dependencies"
187+
)
188+
.unwrap();
189+
writeln!(
190+
temp_file,
191+
"requirements.txt @org/python-maintainers python@company.com #python #dependencies"
192+
)
193+
.unwrap();
194+
writeln!(
195+
temp_file,
196+
"Dockerfile* @org/docker-team docker@company.com #docker #containers"
197+
)
198+
.unwrap();
199+
writeln!(
200+
temp_file,
201+
"*.dockerfile @org/docker-team #docker #containers"
202+
)
203+
.unwrap();
204+
writeln!(temp_file).unwrap();
205+
writeln!(temp_file, "# Root level important files").unwrap();
206+
writeln!(
207+
temp_file,
208+
"README.md @org/maintainers @alice @bob maintainers@company.com #readme #documentation"
209+
)
210+
.unwrap();
211+
writeln!(temp_file, "LICENSE @org/legal legal@company.com #legal").unwrap();
212+
writeln!(temp_file, "CODEOWNERS @org/maintainers #meta").unwrap();
213+
writeln!(temp_file, ".gitignore @org/maintainers #git #configuration").unwrap();
214+
temp_file.flush().unwrap();
215+
216+
c.bench_function("parse_codeowners_complex", |b| {
217+
b.iter(|| parse_codeowners(black_box(temp_file.path())).unwrap())
218+
});
219+
}
220+
221+
// parse_line benchmarks (5 variations)
5222
fn bench_parse_line_simple(c: &mut Criterion) {
6223
let source_path = Path::new("/test/CODEOWNERS");
7224

@@ -16,5 +233,108 @@ fn bench_parse_line_simple(c: &mut Criterion) {
16233
});
17234
}
18235

19-
criterion_group!(benches, bench_parse_line_simple);
236+
fn bench_parse_line_multiple_owners(c: &mut Criterion) {
237+
let source_path = Path::new("/test/CODEOWNERS");
238+
239+
c.bench_function("parse_line_multiple_owners", |b| {
240+
b.iter(|| {
241+
parse_line(
242+
black_box("*.ts @frontend @org/ui-team user@example.com"),
243+
black_box(1),
244+
black_box(source_path),
245+
)
246+
})
247+
});
248+
}
249+
250+
fn bench_parse_line_with_tags(c: &mut Criterion) {
251+
let source_path = Path::new("/test/CODEOWNERS");
252+
253+
c.bench_function("parse_line_with_tags", |b| {
254+
b.iter(|| {
255+
parse_line(
256+
black_box("/security/ @security-team #security #critical"),
257+
black_box(1),
258+
black_box(source_path),
259+
)
260+
})
261+
});
262+
}
263+
264+
fn bench_parse_line_complex(c: &mut Criterion) {
265+
let source_path = Path::new("/test/CODEOWNERS");
266+
267+
c.bench_function("parse_line_complex", |b| {
268+
b.iter(|| {
269+
parse_line(
270+
black_box("/src/components/**/*.tsx @org/frontend @alice @bob user@example.com #ui #react #frontend # Complex component ownership"),
271+
black_box(1),
272+
black_box(source_path),
273+
)
274+
})
275+
});
276+
}
277+
278+
fn bench_parse_line_comment(c: &mut Criterion) {
279+
let source_path = Path::new("/test/CODEOWNERS");
280+
281+
c.bench_function("parse_line_comment", |b| {
282+
b.iter(|| {
283+
parse_line(
284+
black_box("# This is just a comment line"),
285+
black_box(1),
286+
black_box(source_path),
287+
)
288+
})
289+
});
290+
}
291+
292+
// parse_owner benchmarks (5 variations)
293+
fn bench_parse_owner_user(c: &mut Criterion) {
294+
c.bench_function("parse_owner_user", |b| {
295+
b.iter(|| parse_owner(black_box("@username")).unwrap())
296+
});
297+
}
298+
299+
fn bench_parse_owner_team(c: &mut Criterion) {
300+
c.bench_function("parse_owner_team", |b| {
301+
b.iter(|| parse_owner(black_box("@org/frontend-team")).unwrap())
302+
});
303+
}
304+
305+
fn bench_parse_owner_email(c: &mut Criterion) {
306+
c.bench_function("parse_owner_email", |b| {
307+
b.iter(|| parse_owner(black_box("user.name+tag@subdomain.example.com")).unwrap())
308+
});
309+
}
310+
311+
fn bench_parse_owner_unowned(c: &mut Criterion) {
312+
c.bench_function("parse_owner_unowned", |b| {
313+
b.iter(|| parse_owner(black_box("NOOWNER")).unwrap())
314+
});
315+
}
316+
317+
fn bench_parse_owner_unknown(c: &mut Criterion) {
318+
c.bench_function("parse_owner_unknown", |b| {
319+
b.iter(|| parse_owner(black_box("some-random-text-123")).unwrap())
320+
});
321+
}
322+
323+
criterion_group!(
324+
benches,
325+
bench_parse_codeowners_small_file,
326+
bench_parse_codeowners_medium_file,
327+
bench_parse_codeowners_complex_file,
328+
bench_parse_line_simple,
329+
bench_parse_line_multiple_owners,
330+
bench_parse_line_with_tags,
331+
bench_parse_line_complex,
332+
bench_parse_line_comment,
333+
bench_parse_owner_user,
334+
bench_parse_owner_team,
335+
bench_parse_owner_email,
336+
bench_parse_owner_unowned,
337+
bench_parse_owner_unknown
338+
);
20339
criterion_main!(benches);
340+

src/core/parser.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::Path;
44
use super::types::{CodeownersEntry, Owner, OwnerType, Tag};
55

66
/// Parse CODEOWNERS
7-
pub(crate) fn parse_codeowners(source_path: &Path) -> Result<Vec<CodeownersEntry>> {
7+
pub fn parse_codeowners(source_path: &Path) -> Result<Vec<CodeownersEntry>> {
88
let content = std::fs::read_to_string(source_path)?;
99

1010
content
@@ -77,7 +77,7 @@ pub fn parse_line(
7777
}
7878

7979
/// Parse an owner string into an Owner struct
80-
pub(crate) fn parse_owner(owner_str: &str) -> Result<Owner> {
80+
pub fn parse_owner(owner_str: &str) -> Result<Owner> {
8181
let identifier = owner_str.to_string();
8282
let owner_type = if identifier.eq_ignore_ascii_case("NOOWNER") {
8383
OwnerType::Unowned

0 commit comments

Comments
 (0)