@@ -5,15 +5,13 @@ use crate::github::{
5
5
} ;
6
6
use crate :: load:: SiteCtxt ;
7
7
8
- use std:: sync:: Arc ;
9
-
8
+ use hashbrown:: HashMap ;
10
9
use regex:: Regex ;
10
+ use std:: sync:: Arc ;
11
11
12
12
lazy_static:: lazy_static! {
13
13
static ref BODY_TIMER_BUILD : Regex =
14
14
Regex :: new( r"(?:\W|^)@rust-timer\s+build\s+(\w+)(?:\W|$)(?:include=(\S+))?\s*(?:exclude=(\S+))?\s*(?:runs=(\d+))?" ) . unwrap( ) ;
15
- static ref BODY_TIMER_QUEUE : Regex =
16
- Regex :: new( r"(?:\W|^)@rust-timer\s+queue(?:\W|$)(?:include=(\S+))?\s*(?:exclude=(\S+))?\s*(?:runs=(\d+))?" ) . unwrap( ) ;
17
15
}
18
16
19
17
pub async fn handle_github (
@@ -118,26 +116,25 @@ async fn handle_rust_timer(
118
116
return Ok ( github:: Response ) ;
119
117
}
120
118
121
- if let Some ( captures) = BODY_TIMER_QUEUE . captures ( & comment. body ) {
122
- let include = captures. get ( 1 ) . map ( |v| v. as_str ( ) ) ;
123
- let exclude = captures. get ( 2 ) . map ( |v| v. as_str ( ) ) ;
124
- let runs = captures. get ( 3 ) . and_then ( |v| v. as_str ( ) . parse :: < i32 > ( ) . ok ( ) ) ;
125
- {
126
- let conn = ctxt. conn ( ) . await ;
127
- conn. queue_pr ( issue. number , include, exclude, runs) . await ;
128
- }
129
- main_client
130
- . post_comment (
131
- issue. number ,
119
+ if let Some ( queue) = parse_queue_command ( & comment. body ) {
120
+ let msg = match queue {
121
+ Ok ( cmd) => {
122
+ let conn = ctxt. conn ( ) . await ;
123
+ conn. queue_pr ( issue. number , cmd. include , cmd. exclude , cmd. runs )
124
+ . await ;
132
125
format ! (
133
126
"Awaiting bors try build completion.
134
127
135
128
@rustbot label: +S-waiting-on-perf
136
129
137
130
{COMMENT_MARK_TEMPORARY}"
138
- ) ,
139
- )
140
- . await ;
131
+ )
132
+ }
133
+ Err ( error) => {
134
+ format ! ( "Error occurred while parsing comment: {error}" )
135
+ }
136
+ } ;
137
+ main_client. post_comment ( issue. number , msg) . await ;
141
138
return Ok ( github:: Response ) ;
142
139
}
143
140
@@ -163,6 +160,68 @@ async fn handle_rust_timer(
163
160
Ok ( github:: Response )
164
161
}
165
162
163
+ /// Parses the first occurrence of a `@rust-timer queue <...>` command
164
+ /// in the input string.
165
+ fn parse_queue_command ( body : & str ) -> Option < Result < QueueCommand , String > > {
166
+ let prefix = "@rust-timer" ;
167
+ let bot_line = body. lines ( ) . find_map ( |line| {
168
+ let line = line. trim ( ) ;
169
+ line. find ( prefix)
170
+ . map ( |index| line[ index + prefix. len ( ) ..] . trim ( ) )
171
+ } ) ?;
172
+
173
+ let args = bot_line. strip_prefix ( "queue" ) . map ( |l| l. trim ( ) ) ?;
174
+ let mut args = match parse_command_arguments ( args) {
175
+ Ok ( args) => args,
176
+ Err ( error) => return Some ( Err ( error) ) ,
177
+ } ;
178
+ let mut cmd = QueueCommand {
179
+ include : args. remove ( "include" ) ,
180
+ exclude : args. remove ( "exclude" ) ,
181
+ runs : None ,
182
+ } ;
183
+ if let Some ( runs) = args. remove ( "runs" ) {
184
+ let Ok ( runs) = runs. parse :: < u32 > ( ) else {
185
+ return Some ( Err ( format ! ( "Cannot parse runs {runs} as a number" ) ) ) ;
186
+ } ;
187
+ cmd. runs = Some ( runs as i32 ) ;
188
+ }
189
+
190
+ if let Some ( ( key, _) ) = args. into_iter ( ) . next ( ) {
191
+ return Some ( Err ( format ! ( "Unknown command argument {key}" ) ) ) ;
192
+ }
193
+
194
+ Some ( Ok ( cmd) )
195
+ }
196
+
197
+ /// Parses command arguments from a single line of text.
198
+ /// Expects that arguments are separated by whitespace, and each argument
199
+ /// has the format `<key>=<value>`.
200
+ fn parse_command_arguments ( args : & str ) -> Result < HashMap < & str , & str > , String > {
201
+ let mut argmap = HashMap :: new ( ) ;
202
+ for arg in args. split_whitespace ( ) {
203
+ let Some ( ( key, value) ) = arg. split_once ( '=' ) else {
204
+ return Err ( format ! (
205
+ "Invalid command argument `{arg}` (there may be no spaces around the `=` character)"
206
+ ) ) ;
207
+ } ;
208
+ let key = key. trim ( ) ;
209
+ let value = value. trim ( ) ;
210
+ if argmap. insert ( key, value) . is_some ( ) {
211
+ return Err ( format ! ( "Duplicate command argument `{key}`" ) ) ;
212
+ }
213
+ }
214
+
215
+ Ok ( argmap)
216
+ }
217
+
218
+ #[ derive( Debug ) ]
219
+ struct QueueCommand < ' a > {
220
+ include : Option < & ' a str > ,
221
+ exclude : Option < & ' a str > ,
222
+ runs : Option < i32 > ,
223
+ }
224
+
166
225
/// Run the `@rust-timer build` regex over the comment message extracting the commit and the other captures
167
226
fn build_captures ( comment_body : & str ) -> impl Iterator < Item = ( & str , regex:: Captures ) > {
168
227
BODY_TIMER_BUILD
@@ -196,6 +255,7 @@ pub async fn get_authorized_users() -> Result<Vec<u64>, String> {
196
255
#[ cfg( test) ]
197
256
mod tests {
198
257
use super :: * ;
258
+
199
259
#[ test]
200
260
fn captures_all_shas ( ) {
201
261
let comment_body = r#"
@@ -215,4 +275,80 @@ Going to do perf runs for a few of these:
215
275
]
216
276
) ;
217
277
}
278
+
279
+ #[ test]
280
+ fn command_missing ( ) {
281
+ assert ! ( parse_queue_command( "" ) . is_none( ) ) ;
282
+ }
283
+
284
+ #[ test]
285
+ fn unknown_command ( ) {
286
+ assert ! ( parse_queue_command( "@rust-timer foo" ) . is_none( ) ) ;
287
+ }
288
+
289
+ #[ test]
290
+ fn queue_command ( ) {
291
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue" ) ,
292
+ @"Some(Ok(QueueCommand { include: None, exclude: None, runs: None }))" ) ;
293
+ }
294
+
295
+ #[ test]
296
+ fn queue_command_unknown_arg ( ) {
297
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue foo=bar" ) ,
298
+ @r###"Some(Err("Unknown command argument foo"))"### ) ;
299
+ }
300
+
301
+ #[ test]
302
+ fn queue_command_duplicate_arg ( ) {
303
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include=a exclude=c include=b" ) ,
304
+ @r###"Some(Err("Duplicate command argument `include`"))"### ) ;
305
+ }
306
+
307
+ #[ test]
308
+ fn queue_command_include ( ) {
309
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include=abcd,feih" ) ,
310
+ @r###"Some(Ok(QueueCommand { include: Some("abcd,feih"), exclude: None, runs: None }))"### ) ;
311
+ }
312
+
313
+ #[ test]
314
+ fn queue_command_exclude ( ) {
315
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue exclude=foo134,barzbaz41baf" ) ,
316
+ @r###"Some(Ok(QueueCommand { include: None, exclude: Some("foo134,barzbaz41baf"), runs: None }))"### ) ;
317
+ }
318
+
319
+ #[ test]
320
+ fn queue_command_runs ( ) {
321
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue runs=5" ) ,
322
+ @"Some(Ok(QueueCommand { include: None, exclude: None, runs: Some(5) }))" ) ;
323
+ }
324
+
325
+ #[ test]
326
+ fn queue_command_runs_nan ( ) {
327
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue runs=xxx" ) ,
328
+ @r###"Some(Err("Cannot parse runs xxx as a number"))"### ) ;
329
+ }
330
+
331
+ #[ test]
332
+ fn queue_command_combination ( ) {
333
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include=acda,13asd exclude=c13,DA runs=5" ) ,
334
+ @r###"Some(Ok(QueueCommand { include: Some("acda,13asd"), exclude: Some("c13,DA"), runs: Some(5) }))"### ) ;
335
+ }
336
+
337
+ #[ test]
338
+ fn queue_command_argument_spaces ( ) {
339
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include = abcd,das" ) ,
340
+ @r###"Some(Err("Invalid command argument `include` (there may be no spaces around the `=` character)"))"### ) ;
341
+ }
342
+
343
+ #[ test]
344
+ fn queue_command_spaces ( ) {
345
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include=abcd,das " ) ,
346
+ @r###"Some(Ok(QueueCommand { include: Some("abcd,das"), exclude: None, runs: None }))"### ) ;
347
+ }
348
+
349
+ #[ test]
350
+ fn queue_command_with_bors ( ) {
351
+ insta:: assert_compact_debug_snapshot!( parse_queue_command( "@bors try @rust-timer queue include=foo,bar" ) ,
352
+ @r###"Some(Ok(QueueCommand { include: Some("foo,bar"), exclude: None, runs: None }))"### ) ;
353
+ }
218
354
}
0 commit comments