@@ -6,14 +6,8 @@ use crate::github::{
66use crate :: load:: SiteCtxt ;
77
88use hashbrown:: HashMap ;
9- use regex:: Regex ;
109use 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-
1711pub 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+
184211fn 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
198225fn 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 ) ]
250283struct 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-
270289pub 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