@@ -41,6 +41,7 @@ macro_rules! cargo_warn {
41
41
}
42
42
43
43
#[ derive( clap:: Subcommand , Debug , Clone ) ]
44
+ #[ allow( clippy:: large_enum_variant) ]
44
45
pub ( crate ) enum Cmd {
45
46
/// Run the PHD test suite.
46
47
Run {
@@ -68,22 +69,11 @@ pub(crate) enum Cmd {
68
69
69
70
#[ derive( clap:: Parser , Debug , Clone ) ]
70
71
pub ( crate ) struct RunArgs {
71
- /// Build `propolis-server` in release mode.
72
- #[ clap( long = "release" , short = 'r' ) ]
73
- propolis_release_mode : bool ,
74
-
75
72
/// If set, temporary directories older than one day will not be
76
73
/// deleted.
77
74
#[ clap( long) ]
78
75
no_tidy : bool ,
79
76
80
- /// If set, skip migration-from-base tests.
81
- ///
82
- /// If this flag is present, `cargo xtask phd` will not pass
83
- /// `--base-propolis-branch master` to the `phd-runner` command.
84
- #[ clap( long) ]
85
- no_base_propolis : bool ,
86
-
87
77
/// Arguments to pass to `phd-runner run`.
88
78
///
89
79
/// If the `--propolis-server-cmd`, `--crucible-downstairs-commit`,
@@ -95,6 +85,163 @@ pub(crate) struct RunArgs {
95
85
/// `phd-runner`.
96
86
#[ clap( trailing_var_arg = true , allow_hyphen_values = true ) ]
97
87
phd_args : Vec < String > ,
88
+
89
+ #[ clap( flatten) ]
90
+ propolis_args : PropolisArgs ,
91
+
92
+ #[ clap( flatten) ]
93
+ artifact_args : ArtifactStoreArgs ,
94
+
95
+ #[ clap( flatten) ]
96
+ base_propolis_args : BasePropolisArgs ,
97
+
98
+ #[ clap( flatten) ]
99
+ crucible_args : CrucibleArgs ,
100
+ }
101
+
102
+ #[ derive( Debug , Clone , clap:: Parser ) ]
103
+ #[ group( id = "propolis" , required = false , multiple = false ) ]
104
+ #[ command( next_help_heading = "Propolis Selection" ) ]
105
+ struct PropolisArgs {
106
+ /// The command to use to launch the Propolis server.
107
+ ///
108
+ /// If this is not present, a Propolis server binary will be built automatically.
109
+ #[ clap( long = "propolis-server-cmd" , value_parser, value_hint = clap:: ValueHint :: FilePath ) ]
110
+ server_cmd : Option < Utf8PathBuf > ,
111
+
112
+ /// If set, build `propolis-server` in release mode.
113
+ #[ clap( long, short = 'r' ) ]
114
+ release : bool ,
115
+ }
116
+
117
+ #[ derive( Debug , Clone , clap:: Parser ) ]
118
+ #[ group( id = "base-propolis" , required = false , multiple = false ) ]
119
+ #[ command( next_help_heading = "Migration Base Propolis Selection" ) ]
120
+ struct BasePropolisArgs {
121
+ /// Git branch name to use for the "migration base" Propolis server artifact
122
+ /// for migration-from-base tests.
123
+ ///
124
+ /// If this argument is provided, PHD will download the latest Propolis
125
+ /// server artifact from Buildomat for the provided branch name, and use it
126
+ /// to test migration from that Propolis version to the Propolis revision
127
+ /// under test.
128
+ ///
129
+ /// This argument conflicts with the `--base-propolis-commit`,
130
+ /// `--base-propolis-cmd`, and `--no-base-propolis` arguments. If none of
131
+ /// these arguments are provided, `cargo xtask phd` will automatically pass
132
+ /// `--base-propolis-branch master` to `phd-runner`.
133
+ #[ clap( long, value_parser) ]
134
+ base_propolis_branch : Option < String > ,
135
+
136
+ /// Git commit hash to use for the "migration base" Propolis server artifact for
137
+ /// migration from base tests.
138
+ ///
139
+ /// If this argument is provided, PHD will download the Propolis server
140
+ /// artifact from Buildomat for the provided commit hash, and use it
141
+ /// to test migration from that Propolis version to the Propolis revision
142
+ /// under test.
143
+ ///
144
+ /// This argument conflicts with the `--base-propolis-branch`,
145
+ /// `--base-propolis-cmd`, and `--no-base-propolis` arguments. If none of
146
+ /// these arguments are provided, `cargo xtask phd` will automatically pass
147
+ /// `--base-propolis-branch master` to `phd-runner`.
148
+ #[ clap( long, value_parser) ]
149
+ base_propolis_commit : Option < String > ,
150
+
151
+ /// The path of a local command to use as the "migration base" Propolis
152
+ /// server for migration-from-base tests.
153
+ ///
154
+ /// If this argument is provided, PHD will use the provided command to run
155
+ /// to test migration from that Propolis binary to the Propolis revision
156
+ /// under test.
157
+ ///
158
+ /// This argument conflicts with the `--base-propolis-branch`,
159
+ /// `--base-propolis-commit`, and `--no-base-propolis` arguments. If none of
160
+ /// these arguments are provided, `cargo xtask phd` will automatically pass
161
+ /// `--base-propolis-branch master` to `phd-runner`.
162
+ #[ clap(
163
+ long,
164
+ value_hint = clap:: ValueHint :: FilePath ,
165
+ value_parser
166
+ ) ]
167
+ base_propolis_cmd : Option < Utf8PathBuf > ,
168
+
169
+ /// If set, skip migration-from-base tests.
170
+ ///
171
+ /// If this flag is present, `cargo xtask phd` will not pass
172
+ /// `--base-propolis-branch master` to the `phd-runner` command.
173
+ ///
174
+ /// This flag conflicts with the `--base-propolis-branch`,
175
+ /// `--base-propolis-commit`, and `--base-propolis-cmd` arguments. If none
176
+ /// of these arguments are provided, `cargo xtask phd` will automatically
177
+ /// pass `--base-propolis-branch master` to `phd-runner`.
178
+ #[ clap( long) ]
179
+ no_base_propolis : bool ,
180
+ }
181
+
182
+ #[ derive( Debug , Clone , clap:: Parser ) ]
183
+ #[ group( id = "crucible" , required = false , multiple = false ) ]
184
+ #[ command( next_help_heading = "Crucible Downstairs Selection" ) ]
185
+ struct CrucibleArgs {
186
+ /// The path of a local command to use to launch Crucible downstairs
187
+ /// servers.
188
+ ///
189
+ /// This argument conflicts with the `--crucible-downstairs-commit` and
190
+ /// `--no-crucible` arguments. If none of the `--crucible-downstairs-cmd`,
191
+ /// `--crucible-downstairs-commit`, and `--no-crucible` arguments are
192
+ /// provided, then `cargo xtask phd` will pass `--crucible-downstairs-commit
193
+ /// auto` to `phd-runner`.
194
+ #[ clap( long, value_parser, value_hint = clap:: ValueHint :: FilePath ) ]
195
+ crucible_downstairs_cmd : Option < Utf8PathBuf > ,
196
+
197
+ /// Git revision to use to download Crucible downstairs artifacts from
198
+ /// Buildomat.
199
+ ///
200
+ /// This may either be the string 'auto' or a 40-character Git commit
201
+ /// hash. If this is 'auto', then the Git revision of Crucible is determined
202
+ /// automatically based on the Propolis workspace's Cargo git dependency on
203
+ /// the `crucible` crate (determined when `phd-runner` is built). If an
204
+ /// explicit commit hash is provided, that commit is downloaded from
205
+ /// Buildomat, regardless of which version of the `crucible` crate Propolis
206
+ /// depends on.
207
+ ///
208
+ /// This argument conflicts with the `--crucible-downstairs-cmd` and
209
+ /// `--no-crucible` arguments. If none of the `--crucible-downstairs-cmd`,
210
+ /// `--crucible-downstairs-commit`, and `--no-crucible` arguments are
211
+ /// provided, then `cargo xtask phd` will pass `--crucible-downstairs-commit
212
+ /// auto` to `phd-runner`.
213
+ #[ clap( long, value_parser) ]
214
+ crucible_downstairs_commit : Option < String > ,
215
+
216
+ /// If set, skip Crucible tests.
217
+ ///
218
+ /// If this flag is present, `cargo xtask phd` will not pass
219
+ /// `--crucible-downstairs-commit auto` to the `phd-runner` command.
220
+ ///
221
+ /// This flag conflicts with the `--crucible-downstairs-cmd` and
222
+ /// `--crucible-downstairs-commit` arguments. If none of the
223
+ /// `--crucible-downstairs-cmd`, `--crucible-downstairs-commit`, and
224
+ /// `--no-crucible` arguments are provided, then `cargo xtask phd` will pass
225
+ /// `--crucible-downstairs-commit auto` to `phd-runner`.
226
+ #[ clap( long) ]
227
+ no_crucible : bool ,
228
+ }
229
+
230
+ #[ derive( Debug , Clone , clap:: Parser ) ]
231
+ #[ command( next_help_heading = "Artifact Store Options" ) ]
232
+ struct ArtifactStoreArgs {
233
+ /// The path to a TOML file describing the artifact store to use for this
234
+ /// run.
235
+ #[ clap( long, value_parser, value_hint = clap:: ValueHint :: FilePath ) ]
236
+ artifact_toml_path : Option < Utf8PathBuf > ,
237
+
238
+ /// The directory in which artifacts (guest OS images, bootroms, etc.)
239
+ /// are to be stored.
240
+ ///
241
+ /// If this argument is not provided, the default artifact store directory
242
+ /// will be created in `target/phd/artifacts`.
243
+ #[ clap( long, value_parser) ]
244
+ artifact_directory : Option < Utf8PathBuf > ,
98
245
}
99
246
100
247
impl Cmd {
@@ -109,10 +256,12 @@ impl Cmd {
109
256
let now = time:: SystemTime :: now ( ) ;
110
257
111
258
let RunArgs {
112
- propolis_release_mode,
113
259
no_tidy,
260
+ propolis_args,
114
261
phd_args,
115
- no_base_propolis,
262
+ artifact_args,
263
+ base_propolis_args,
264
+ crucible_args,
116
265
} = match self {
117
266
Self :: Run { args } => args,
118
267
Self :: Tidy => {
@@ -137,66 +286,13 @@ impl Cmd {
137
286
}
138
287
} ;
139
288
140
- // Bash-script-style arg parsing, rather than using `clap`, because we
141
- // want to filter out the args we default regardless of their position in
142
- // the input. A `clap` parser can only accept unrecognized args if they're
143
- // trailing after all recognized args, which isn't the behavior we want, as
144
- // we don't know what order the `phd-runner` command line will come in.
145
- let mut arg_iter = phd_args. iter ( ) . map ( String :: as_str) ;
146
- let mut bonus_args = Vec :: new ( ) ;
147
- let mut propolis_base_branch = None ;
148
- // If set, the `base-propolis-cmd` or `base-propolis-commit` CLI args
149
- // were passed, so we should skip adding `--base-propolis-branch`.
150
- let mut overridden_base_propolis = no_base_propolis;
151
- let mut propolis_local_path = None ;
152
- let mut crucible_commit = None ;
153
- let mut artifacts_toml = None ;
154
- let mut artifact_dir = None ;
155
- while let Some ( arg) = arg_iter. next ( ) {
156
- macro_rules! take_next_arg {
157
- ( $var: ident) => { {
158
- let val = arg_iter. next( ) . ok_or_else( || {
159
- anyhow:: anyhow!( "Missing value for argument `{}`" , arg)
160
- } ) ?;
161
- cargo_log!( "Overridden" , "{} {val:?}" , arg) ;
162
- $var = Some ( val) ;
163
- } } ;
164
- }
165
- match arg {
166
- args:: PROPOLIS_BASE => take_next_arg ! ( propolis_base_branch) ,
167
- args:: PROPOLIS_CMD => take_next_arg ! ( propolis_local_path) ,
168
- args:: CRUCIBLE_COMMIT => take_next_arg ! ( crucible_commit) ,
169
- args:: ARTIFACTS_TOML => take_next_arg ! ( artifacts_toml) ,
170
- args:: ARTIFACTS_DIR => take_next_arg ! ( artifact_dir) ,
171
- args:: PROPOLIS_BASE_COMMIT | args:: PROPOLIS_BASE_CMD => {
172
- overridden_base_propolis = true ;
173
- bonus_args. push ( arg) ;
174
-
175
- let val = arg_iter. next ( ) . ok_or_else ( || {
176
- anyhow:: anyhow!( "Missing value for argument `{arg}`" )
177
- } ) ?;
178
- bonus_args. push ( val) ;
179
- cargo_log ! ( "Overridden" , "{} {val:?}" , arg) ;
180
- }
181
-
182
- _ => bonus_args. push ( arg) ,
183
- }
184
- }
185
-
186
- let propolis_local_path = match propolis_local_path {
187
- Some ( path) => {
188
- if propolis_release_mode {
189
- cargo_warn ! (
190
- "setting `--release` to build propolis-server in release mode \
191
- does nothing if an existing propolis binary path is \
192
- provided using `{}`",
193
- args:: PROPOLIS_CMD ,
194
- ) ;
195
- }
196
- path. into ( )
289
+ let propolis_local_path = match propolis_args. server_cmd {
290
+ Some ( cmd) => {
291
+ cargo_log ! ( "Using" , "local Propolis server command {cmd}" ) ;
292
+ cmd
197
293
}
198
294
None => {
199
- let bin = build_bin ( "propolis-server" , propolis_release_mode ) ?;
295
+ let bin = build_bin ( "propolis-server" , propolis_args . release ) ?;
200
296
let path = bin
201
297
. path ( )
202
298
. try_into ( )
@@ -205,8 +301,10 @@ impl Cmd {
205
301
}
206
302
} ;
207
303
208
- let artifact_dir =
209
- artifact_dir. map ( Utf8PathBuf :: from) . unwrap_or_else ( || {
304
+ let artifact_dir = artifact_args
305
+ . artifact_directory
306
+ . map ( Utf8PathBuf :: from)
307
+ . unwrap_or_else ( || {
210
308
// if there's no explicitly overridden `artifact_dir` path, use
211
309
// `target/phd/artifacts`.
212
310
phd_dir. join ( "artifacts" )
@@ -234,8 +332,10 @@ impl Cmd {
234
332
235
333
mkdir ( & tmp_dir, "temp directory" ) ?;
236
334
237
- let artifacts_toml =
238
- artifacts_toml. map ( Utf8PathBuf :: from) . unwrap_or_else ( || {
335
+ let artifacts_toml = artifact_args
336
+ . artifact_toml_path
337
+ . map ( Utf8PathBuf :: from)
338
+ . unwrap_or_else ( || {
239
339
// if there's no explicitly overridden `artifacts.toml` path,
240
340
// determine the default one from the workspace path.
241
341
relativize ( & meta. workspace_root )
@@ -252,36 +352,63 @@ impl Cmd {
252
352
let phd_runner = build_bin ( "phd-runner" , false ) ?;
253
353
let mut cmd = phd_runner. command ( ) ;
254
354
cmd. arg ( "run" )
255
- . arg ( args :: PROPOLIS_CMD )
355
+ . arg ( "--propolis-server-cmd" )
256
356
. arg ( & propolis_local_path)
257
- . arg ( args:: CRUCIBLE_COMMIT )
258
- . arg ( crucible_commit. unwrap_or ( "auto" ) )
259
- . arg ( args:: ARTIFACTS_TOML )
357
+ . arg ( "--artifact-toml-path" )
260
358
. arg ( & artifacts_toml)
261
- . arg ( args :: ARTIFACTS_DIR )
359
+ . arg ( "--artifact-directory" )
262
360
. arg ( & artifact_dir)
263
361
. arg ( "--tmp-directory" )
264
- . arg ( & tmp_dir)
265
- . args ( bonus_args) ;
362
+ . arg ( & tmp_dir) ;
363
+ crucible_args. configure_command ( & mut cmd) ;
364
+ base_propolis_args. configure_command ( & mut cmd) ;
365
+ cmd. args ( phd_args) ;
266
366
267
- if !overridden_base_propolis {
268
- cmd. arg ( args:: PROPOLIS_BASE )
269
- . arg ( propolis_base_branch. unwrap_or ( "master" ) ) ;
270
- }
271
367
let status = run_exit_code ( & mut cmd) ?;
272
368
273
369
std:: process:: exit ( status) ;
274
370
}
275
371
}
276
372
277
- mod args {
278
- pub ( super ) const PROPOLIS_CMD : & str = "--propolis-server-cmd" ;
279
- pub ( super ) const PROPOLIS_BASE : & str = "--base-propolis-branch" ;
280
- pub ( super ) const PROPOLIS_BASE_COMMIT : & str = "--base-propolis-commit" ;
281
- pub ( super ) const PROPOLIS_BASE_CMD : & str = "--base-propolis-cmd" ;
282
- pub ( super ) const CRUCIBLE_COMMIT : & str = "--crucible-downstairs-commit" ;
283
- pub ( super ) const ARTIFACTS_TOML : & str = "--artifact-toml-path" ;
284
- pub ( super ) const ARTIFACTS_DIR : & str = "--artifact-directory" ;
373
+ impl CrucibleArgs {
374
+ fn configure_command ( & self , cmd : & mut Command ) {
375
+ if let Some ( ref path) = self . crucible_downstairs_cmd {
376
+ cargo_log ! ( "Using" , "local Crucible downstairs: {path}" ) ;
377
+ cmd. arg ( "--crucible-downstairs-cmd" ) . arg ( path) ;
378
+ } else if let Some ( ref commit) = self . crucible_downstairs_commit {
379
+ cargo_log ! ( "Using" , "Crucible downstairs from commit: {commit}" ) ;
380
+ cmd. arg ( "--crucible-downstairs-commit" ) . arg ( commit) ;
381
+ } else if self . no_crucible {
382
+ cargo_log ! ( "Skipping" , "Crucible tests" ) ;
383
+ } else {
384
+ cmd. arg ( "--crucible-downstairs-commit" ) . arg ( "auto" ) ;
385
+ }
386
+ }
387
+ }
388
+
389
+ impl BasePropolisArgs {
390
+ fn configure_command ( & self , cmd : & mut Command ) {
391
+ if let Some ( ref path) = self . base_propolis_cmd {
392
+ cargo_log ! ( "Using" , "local migration-base Propolis: {path}" ) ;
393
+ cmd. arg ( "--base-propolis-cmd" ) . arg ( path) ;
394
+ } else if let Some ( ref commit) = self . base_propolis_commit {
395
+ cargo_log ! (
396
+ "Using" ,
397
+ "migration-base Propolis from commit: {commit}"
398
+ ) ;
399
+ cmd. arg ( "--base-propolis-commit" ) . arg ( commit) ;
400
+ } else if let Some ( ref branch) = self . base_propolis_branch {
401
+ cargo_log ! (
402
+ "Using" ,
403
+ "migration-base Propolis from branch: {branch}"
404
+ ) ;
405
+ cmd. arg ( "--base-propolis-branch" ) . arg ( branch) ;
406
+ } else if self . no_base_propolis {
407
+ cargo_log ! ( "Skipping" , "migration-from-base tests" ) ;
408
+ } else {
409
+ cmd. arg ( "--base-propolis-branch" ) . arg ( "master" ) ;
410
+ }
411
+ }
285
412
}
286
413
287
414
fn build_bin (
0 commit comments