Skip to content

Commit f3bcb8b

Browse files
committed
Refactor build command parsing
1 parent b660902 commit f3bcb8b

File tree

1 file changed

+112
-48
lines changed

1 file changed

+112
-48
lines changed

site/src/request_handlers/github.rs

Lines changed: 112 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,8 @@ use crate::github::{
66
use crate::load::SiteCtxt;
77

88
use hashbrown::HashMap;
9-
use regex::Regex;
109
use std::sync::Arc;
1110

12-
lazy_static::lazy_static! {
13-
static ref BODY_TIMER_BUILD: Regex =
14-
Regex::new(r"(?:\W|^)@rust-timer\s+build\s+(\w+)(?:\W|$)(?:include=(\S+))?\s*(?:exclude=(\S+))?\s*(?:runs=(\d+))?").unwrap();
15-
}
16-
1711
pub async fn handle_github(
1812
request: github::Request,
1913
ctxt: Arc<SiteCtxt>,
@@ -143,13 +137,30 @@ async fn handle_rust_timer(
143137
return Ok(github::Response);
144138
}
145139

146-
for captures in build_captures(&comment.body).map(|(_, captures)| captures) {
147-
let include = captures.get(2).map(|v| v.as_str());
148-
let exclude = captures.get(3).map(|v| v.as_str());
149-
let runs = captures.get(4).and_then(|v| v.as_str().parse::<i32>().ok());
150-
{
151-
let conn = ctxt.conn().await;
152-
conn.queue_pr(issue.number, include, exclude, runs).await;
140+
let build_cmds: Vec<_> = parse_build_commands(&comment.body).collect();
141+
let mut valid_build_cmds = vec![];
142+
let mut errors = String::new();
143+
for cmd in build_cmds {
144+
match cmd {
145+
Ok(cmd) => valid_build_cmds.push(cmd),
146+
Err(error) => errors.push_str(&format!("Cannot parse build command: {error}\n")),
147+
}
148+
}
149+
if !errors.is_empty() {
150+
main_client.post_comment(issue.number, errors).await;
151+
return Ok(github::Response);
152+
}
153+
154+
{
155+
let conn = ctxt.conn().await;
156+
for command in &valid_build_cmds {
157+
conn.queue_pr(
158+
issue.number,
159+
command.params.include,
160+
command.params.exclude,
161+
command.params.runs,
162+
)
163+
.await;
153164
}
154165
}
155166

@@ -158,7 +169,7 @@ async fn handle_rust_timer(
158169
main_client,
159170
ci_client,
160171
issue.number,
161-
build_captures(&comment.body).map(|(commit, _)| commit),
172+
valid_build_cmds.iter().map(|c| c.sha),
162173
)
163174
.await?;
164175

@@ -181,6 +192,22 @@ fn parse_queue_command(body: &str) -> Option<Result<QueueCommand, String>> {
181192
Some(Ok(QueueCommand { params }))
182193
}
183194

195+
/// Parses all occurrences of a `@rust-timer build <shared-args>` command in the input string.
196+
fn parse_build_commands(body: &str) -> impl Iterator<Item = Result<BuildCommand, String>> {
197+
get_command_lines(body, "build").map(|line| {
198+
let mut iter = line.splitn(2, ' ');
199+
let Some(sha) = iter.next().filter(|s| !s.is_empty() && !s.contains('=')) else {
200+
return Err("Missing SHA in build command".to_string());
201+
};
202+
203+
let sha = sha.trim_start_matches("https://github.com/rust-lang/rust/commit/");
204+
let args = iter.next().unwrap_or("");
205+
let args = parse_command_arguments(args)?;
206+
let params = parse_benchmark_parameters(args)?;
207+
Ok(BuildCommand { sha, params })
208+
})
209+
}
210+
184211
fn get_command_lines<'a: 'b, 'b>(
185212
body: &'a str,
186213
command: &'b str,
@@ -192,7 +219,7 @@ fn get_command_lines<'a: 'b, 'b>(
192219
.map(|index| line[index + prefix.len()..].trim())
193220
})
194221
.filter_map(move |line| line.strip_prefix(command))
195-
.map(move |l| l.trim())
222+
.map(move |l| l.trim_start())
196223
}
197224

198225
fn parse_benchmark_parameters<'a>(
@@ -246,27 +273,19 @@ struct QueueCommand<'a> {
246273
params: BenchmarkParameters<'a>,
247274
}
248275

276+
#[derive(Debug)]
277+
struct BuildCommand<'a> {
278+
sha: &'a str,
279+
params: BenchmarkParameters<'a>,
280+
}
281+
249282
#[derive(Debug)]
250283
struct BenchmarkParameters<'a> {
251284
include: Option<&'a str>,
252285
exclude: Option<&'a str>,
253286
runs: Option<i32>,
254287
}
255288

256-
/// Run the `@rust-timer build` regex over the comment message extracting the commit and the other captures
257-
fn build_captures(comment_body: &str) -> impl Iterator<Item = (&str, regex::Captures)> {
258-
BODY_TIMER_BUILD
259-
.captures_iter(comment_body)
260-
.filter_map(|captures| {
261-
captures.get(1).map(|m| {
262-
let commit = m
263-
.as_str()
264-
.trim_start_matches("https://github.com/rust-lang/rust/commit/");
265-
(commit, captures)
266-
})
267-
})
268-
}
269-
270289
pub async fn get_authorized_users() -> Result<Vec<u64>, String> {
271290
let url = format!("{}/permissions/perf.json", ::rust_team_data::v1::BASE_URL);
272291
let client = reqwest::Client::new();
@@ -288,32 +307,62 @@ mod tests {
288307
use super::*;
289308

290309
#[test]
291-
fn captures_all_shas() {
292-
let comment_body = r#"
293-
Going to do perf runs for a few of these:
294-
295-
@rust-timer build 5832462aa1d9373b24ace96ad2c50b7a18af9952 (https://github.com/rust-lang/rust/pull/100307)
296-
@rust-timer build 23936af287657fa4148aeab40cc2a780810fae52 (https://github.com/rust-lang/rust/pull/100392)
297-
"#;
298-
let shas = build_captures(comment_body)
299-
.map(|(c, _)| c)
300-
.collect::<Vec<_>>();
301-
assert_eq!(
302-
shas,
303-
&[
304-
"5832462aa1d9373b24ace96ad2c50b7a18af9952",
305-
"23936af287657fa4148aeab40cc2a780810fae52"
306-
]
307-
);
310+
fn build_command_missing() {
311+
assert!(get_build_commands("").is_empty());
312+
}
313+
314+
#[test]
315+
fn build_unknown_command() {
316+
assert!(get_build_commands("@rust-timer foo").is_empty());
308317
}
309318

310319
#[test]
311-
fn command_missing() {
320+
fn build_command_missing_sha() {
321+
insta::assert_compact_debug_snapshot!(get_build_commands("@rust-timer build"),
322+
@r###"[Err("Missing SHA in build command")]"###);
323+
}
324+
325+
#[test]
326+
fn build_command() {
327+
insta::assert_compact_debug_snapshot!(get_build_commands("@rust-timer build 5832462aa1d9373b24ace96ad2c50b7a18af9952"),
328+
@r###"[Ok(BuildCommand { sha: "5832462aa1d9373b24ace96ad2c50b7a18af9952", params: BenchmarkParameters { include: None, exclude: None, runs: None } })]"###);
329+
}
330+
331+
#[test]
332+
fn build_command_multiple() {
333+
insta::assert_compact_debug_snapshot!(get_build_commands(r#"
334+
@rust-timer build 5832462aa1d9373b24ace96ad2c50b7a18af9952
335+
@rust-timer build 23936af287657fa4148aeab40cc2a780810fae52
336+
"#),
337+
@r###"[Ok(BuildCommand { sha: "5832462aa1d9373b24ace96ad2c50b7a18af9952", params: BenchmarkParameters { include: None, exclude: None, runs: None } }), Ok(BuildCommand { sha: "23936af287657fa4148aeab40cc2a780810fae52", params: BenchmarkParameters { include: None, exclude: None, runs: None } })]"###);
338+
}
339+
340+
#[test]
341+
fn build_command_unknown_arg() {
342+
insta::assert_compact_debug_snapshot!(get_build_commands("@rust-timer build foo=bar"),
343+
@r###"[Err("Missing SHA in build command")]"###);
344+
}
345+
346+
#[test]
347+
fn build_command_complex() {
348+
insta::assert_compact_debug_snapshot!(get_build_commands(" @rust-timer build sha123456 exclude=baz include=foo,bar runs=4"),
349+
@r###"[Ok(BuildCommand { sha: "sha123456", params: BenchmarkParameters { include: Some("foo,bar"), exclude: Some("baz"), runs: Some(4) } })]"###);
350+
}
351+
352+
#[test]
353+
fn build_command_link() {
354+
insta::assert_compact_debug_snapshot!(get_build_commands(r#"
355+
@rust-timer build https://github.com/rust-lang/rust/commit/323f521bc6d8f2b966ba7838a3f3ee364e760b7e"#),
356+
@r###"[Ok(BuildCommand { sha: "323f521bc6d8f2b966ba7838a3f3ee364e760b7e", params: BenchmarkParameters { include: None, exclude: None, runs: None } })]"###);
357+
}
358+
359+
#[test]
360+
fn queue_command_missing() {
312361
assert!(parse_queue_command("").is_none());
313362
}
314363

315364
#[test]
316-
fn unknown_command() {
365+
fn queue_unknown_command() {
317366
assert!(parse_queue_command("@rust-timer foo").is_none());
318367
}
319368

@@ -388,4 +437,19 @@ Going to do perf runs for a few of these:
388437
insta::assert_compact_debug_snapshot!(parse_queue_command("@rust-timer queue runs=3 exclude=c,a include=b"),
389438
@r###"Some(Ok(QueueCommand { params: BenchmarkParameters { include: Some("b"), exclude: Some("c,a"), runs: Some(3) } }))"###);
390439
}
440+
441+
#[test]
442+
fn queue_command_multiline() {
443+
insta::assert_compact_debug_snapshot!(parse_queue_command(r#"Ok, this looks good now.
444+
Let's do a perf run quickly and then we can merge it.
445+
446+
@bors try @rust-timer queue include=foo,bar
447+
448+
Otherwise LGTM."#),
449+
@r###"Some(Ok(QueueCommand { params: BenchmarkParameters { include: Some("foo,bar"), exclude: None, runs: None } }))"###);
450+
}
451+
452+
fn get_build_commands(body: &str) -> Vec<Result<BuildCommand, String>> {
453+
parse_build_commands(body).collect()
454+
}
391455
}

0 commit comments

Comments
 (0)