use std::sync::Arc;
use crate::ids::TargetHandle;
use crate::proto::test::{ConfiguredTargetHandle, ExternalRunnerSpec, ExternalRunnerSpecValue};
use crate::proto::test::external_runner_spec_value::Value;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandArg {
Verbatim(String),
ArgHandle(i64),
EnvHandle(String),
}
impl CommandArg {
pub fn from_proto(v: ExternalRunnerSpecValue) -> Self {
match v.value.unwrap() {
Value::Verbatim(s) => CommandArg::Verbatim(s),
Value::ArgHandle(i) => CommandArg::ArgHandle(i),
Value::EnvHandle(s) => CommandArg::EnvHandle(s),
}
}
}
#[derive(Debug)]
pub struct TargetSpec {
pub handle: TargetHandle,
pub suite: String,
pub display: String,
pub test_type: String,
pub command: Box<[CommandArg]>,
pub env: Box<[(String, CommandArg)]>,
pub labels: Vec<String>,
pub contacts: Vec<String>,
pub oncall: Option<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum SpecError {
#[error("ExternalRunnerSpec missing target")]
MissingTarget,
#[error("ExternalRunnerSpec target missing handle")]
MissingHandle,
}
impl TargetSpec {
pub fn from_proto(spec: ExternalRunnerSpec) -> Result<Arc<Self>, SpecError> {
let target = spec.target.ok_or(SpecError::MissingTarget)?;
let handle = target.handle.ok_or(SpecError::MissingHandle)?;
let display = format!("{}//{}:{}", target.cell, target.package, target.target);
let labels = spec.labels;
let mut env: Vec<(String, CommandArg)> = spec
.env
.into_iter()
.map(|(k, v)| (k, CommandArg::from_proto(v)))
.collect();
env.sort_by(|a, b| a.0.cmp(&b.0));
let command = spec
.command
.into_iter()
.map(CommandArg::from_proto)
.collect::<Vec<_>>()
.into_boxed_slice();
Ok(Arc::new(TargetSpec {
handle: TargetHandle(handle.id),
suite: display.clone(),
display,
test_type: spec.test_type,
command,
env: env.into_boxed_slice(),
labels,
contacts: spec.contacts,
oncall: spec.oncall,
}))
}
pub fn handle_proto(&self) -> ConfiguredTargetHandle {
ConfiguredTargetHandle { id: self.handle.0 }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proto::test::ConfiguredTarget;
use std::collections::HashMap;
fn verbatim(s: &str) -> ExternalRunnerSpecValue {
ExternalRunnerSpecValue {
value: Some(Value::Verbatim(s.to_owned())),
}
}
#[test]
fn parses_target_and_sorts_env() {
let mut env = HashMap::new();
env.insert("ZED".to_owned(), verbatim("1"));
env.insert("ABLE".to_owned(), verbatim("2"));
let spec = ExternalRunnerSpec {
target: Some(ConfiguredTarget {
handle: Some(ConfiguredTargetHandle { id: 42 }),
cell: "root".into(),
package: "rust/foo".into(),
target: "foo".into(),
configuration: "cfg".into(),
package_project_relative_path: "rust/foo".into(),
test_config_unification_rollout: false,
package_oncall: None,
}),
test_type: "rust".into(),
command: vec![
verbatim("./test-bin"),
ExternalRunnerSpecValue {
value: Some(Value::ArgHandle(7)),
},
],
env,
labels: vec!["rust:flaky".into(), "foo".into()],
contacts: vec!["team@x".into()],
oncall: Some("oncall-x".into()),
working_dir_cell: "root".into(),
};
let parsed = TargetSpec::from_proto(spec).unwrap();
assert_eq!(parsed.handle, TargetHandle(42));
assert_eq!(parsed.suite, "root//rust/foo:foo");
assert_eq!(parsed.display, "root//rust/foo:foo");
assert_eq!(parsed.test_type, "rust");
assert_eq!(parsed.command.len(), 2);
assert_eq!(parsed.env[0].0, "ABLE");
assert_eq!(parsed.env[1].0, "ZED");
assert_eq!(parsed.labels.len(), 2);
assert_eq!(parsed.labels[0], "rust:flaky");
}
#[test]
fn missing_target_is_error() {
let spec = ExternalRunnerSpec {
target: None,
..Default::default()
};
assert!(matches!(
TargetSpec::from_proto(spec),
Err(SpecError::MissingTarget)
));
}
}