use std::path::PathBuf;
#[derive(clap::Args, Debug, Default)]
pub struct CertifyArgs {
#[arg(long)]
pub pr: Option<u64>,
#[arg(long, value_delimiter = ',', value_name = "ID,...")]
pub gates: Vec<String>,
#[arg(long, value_name = "N")]
pub shards: Option<usize>,
#[arg(long, visible_alias = "plan")]
pub dry_run: bool,
#[arg(long)]
pub json: bool,
#[arg(long)]
pub keep_going: bool,
#[arg(long)]
pub yes: bool,
#[arg(long)]
pub hardware: Option<String>,
#[arg(long, value_name = "SHA")]
pub anchor: Option<String>,
#[arg(long, value_name = "REMOTE/BRANCH")]
pub guard_ref: Option<String>,
#[arg(long, conflicts_with = "guard_ref")]
pub no_guard: bool,
#[arg(long, default_value_t = 3.0)]
pub timeout_factor: f64,
#[arg(long, value_name = "DIR")]
pub out: Option<PathBuf>,
#[arg(long, value_delimiter = ',', value_name = "NODE,...")]
pub with_nodes: Vec<String>,
#[arg(long, requires = "with_nodes")]
pub remote_only: bool,
#[arg(long, requires = "with_nodes", value_name = "NODE")]
pub energy_reference_node: Option<String>,
#[arg(long, value_name = "PATH")]
pub metralectl: Option<PathBuf>,
#[arg(long)]
pub no_serve_reuse: bool,
#[arg(long)]
pub dangerous_ignore_thermals: bool,
}
impl CertifyArgs {
pub fn validate(&self) -> Result<(), String> {
if self.remote_only && self.with_nodes.is_empty() {
return Err("--remote-only needs --with-nodes: nothing would run anywhere".into());
}
if let Some(r) = &self.energy_reference_node
&& !self.with_nodes.contains(r)
{
return Err(format!(
"--energy-reference-node {r} is not one of --with-nodes ({}): its Speed-class and \
energy-bounded gates would run on another box",
self.with_nodes.join(", ")
));
}
if !(self.timeout_factor.is_finite() && self.timeout_factor >= 1.0) {
return Err(format!(
"--timeout-factor must be a finite number >= 1, got {}",
self.timeout_factor
));
}
if self.shards == Some(0) {
return Err("--shards must be at least 1".into());
}
for g in &self.gates {
if !metrale_bench::gate::REQUIRED_GATES.contains(&g.as_str()) {
return Err(format!(
"--gates names {g}, which is not a required gate ({})",
metrale_bench::gate::REQUIRED_GATES.join(", ")
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn args() -> CertifyArgs {
CertifyArgs {
timeout_factor: 3.0,
..CertifyArgs::default()
}
}
#[test]
fn defaults_validate() {
assert_eq!(args().validate(), Ok(()));
}
#[test]
fn remote_only_without_nodes_is_refused() {
let mut a = args();
a.remote_only = true;
assert!(a.validate().unwrap_err().contains("--with-nodes"));
}
#[test]
fn a_legacy_shard_id_is_not_a_gate() {
let mut a = args();
a.gates = vec!["bfcl-subset-a".into()];
assert!(a.validate().unwrap_err().contains("not a required gate"));
}
#[test]
fn zero_shards_is_refused_and_one_is_the_whole_draw() {
let mut a = args();
a.shards = Some(0);
assert!(a.validate().unwrap_err().contains("--shards"));
a.shards = Some(1);
assert_eq!(a.validate(), Ok(()));
}
#[test]
fn an_unknown_gate_is_refused_with_the_list() {
let mut a = args();
a.gates = vec!["nope".into()];
assert!(a.validate().unwrap_err().contains("decode-floor"));
}
#[test]
fn an_energy_reference_outside_with_nodes_is_refused() {
let mut a = args();
a.energy_reference_node = Some("b:9000".into());
assert!(
a.validate()
.unwrap_err()
.contains("not one of --with-nodes")
);
a.with_nodes = vec!["a:9000".into(), "b".into()];
let e = a.validate().unwrap_err();
assert!(e.contains("b:9000") && e.contains("a:9000, b"), "{e}");
a.with_nodes.push("b:9000".into());
assert_eq!(a.validate(), Ok(()));
}
#[test]
fn a_timeout_factor_below_one_is_refused() {
let mut a = args();
a.timeout_factor = 0.5;
assert!(a.validate().is_err());
a.timeout_factor = f64::NAN;
assert!(a.validate().is_err());
}
}