use std::env::VarError;
use crate::cli_chains::action::{Action, ActionKind};
use crate::cli_chains::corpus::{self, Case, MINIMUM_LOCAL_CASES};
use crate::cli_chains::coverage::Entry;
use crate::cli_chains::ids::{CaseId, SELECT_VARIABLE};
#[must_use]
pub fn default_selection() -> Vec<&'static Case> {
corpus::corpus().cases.iter().collect()
}
#[must_use]
pub fn completeness_problems(cases: &[&Case]) -> Vec<String> {
let inventory = &corpus::corpus().cases;
let mut problems = Vec::new();
if cases.len() < MINIMUM_LOCAL_CASES {
problems.push(format!(
"only {} cases execute; at least {MINIMUM_LOCAL_CASES} are required",
cases.len()
));
}
if cases.len() != inventory.len() {
problems.push(format!(
"execution count {} does not equal inventory count {}",
cases.len(),
inventory.len()
));
}
for (index, expected) in inventory.iter().enumerate() {
match cases.get(index) {
Some(observed)
if observed.id == expected.id && observed.fingerprint == expected.fingerprint => {}
Some(observed) => problems.push(format!(
"inventory position {} expected {} fp={:016x}, observed {} fp={:016x}",
index + 1,
expected.id,
expected.fingerprint,
observed.id,
observed.fingerprint
)),
None => problems.push(format!("{} is not selected", expected.id)),
}
}
problems
}
pub fn selected_case() -> Result<Option<&'static Case>, String> {
selection_from(std::env::var(SELECT_VARIABLE))
}
pub fn selection_from(value: Result<String, VarError>) -> Result<Option<&'static Case>, String> {
let text = match value {
Ok(text) => text,
Err(VarError::NotPresent) => return Ok(None),
Err(VarError::NotUnicode(raw)) => {
return Err(format!("{SELECT_VARIABLE}={raw:?} is not text"));
}
};
let id = CaseId::parse(text.trim()).ok_or_else(|| {
format!("{SELECT_VARIABLE}={text:?} is not a stable identifier such as local-0001")
})?;
corpus::corpus()
.case(id)
.map(Some)
.ok_or_else(|| format!("{SELECT_VARIABLE}={id} names no case in the corpus"))
}
#[derive(Debug, Clone, Copy)]
pub struct Area {
pub name: &'static str,
pub kinds: &'static [ActionKind],
pub needs_refusal: bool,
}
pub const AREAS: [Area; 7] = [
Area {
name: "host",
kinds: &[
ActionKind::HostSetCapacity,
ActionKind::HostSetRuntimeRoot,
ActionKind::HostResetRuntimeRoot,
ActionKind::HostShow,
],
needs_refusal: true,
},
Area {
name: "repository",
kinds: &[
ActionKind::RepoAdd,
ActionKind::RepoList,
ActionKind::RepoSetCapacity,
ActionKind::RepoSetScale,
ActionKind::RepoAddLabel,
ActionKind::RepoRemoveLabel,
ActionKind::RepoRemove,
],
needs_refusal: true,
},
Area {
name: "organization",
kinds: &[
ActionKind::OrgAdd,
ActionKind::OrgList,
ActionKind::OrgSetCapacity,
ActionKind::OrgSetScale,
ActionKind::OrgAddLabel,
ActionKind::OrgRemoveLabel,
ActionKind::OrgRemove,
],
needs_refusal: true,
},
Area {
name: "workspace",
kinds: &[ActionKind::RepoSetWorkspace],
needs_refusal: true,
},
Area {
name: "status",
kinds: &[ActionKind::StatusJson],
needs_refusal: false,
},
Area {
name: "authentication setup",
kinds: &[ActionKind::AuthLogin],
needs_refusal: false,
},
Area {
name: "authentication teardown",
kinds: &[ActionKind::AuthLogout],
needs_refusal: false,
},
];
#[must_use]
pub fn area_counts(cases: &[&Case]) -> Vec<(Area, usize, usize)> {
let mut actions: Vec<(ActionKind, bool)> = Vec::new();
for case in cases {
for entry in case.trace().entries {
if let Entry::Run {
action, transition, ..
} = entry
{
actions.push((action.kind(), transition.exit.is_success()));
}
}
}
AREAS
.iter()
.map(|area| {
let of_area = actions.iter().filter(|(kind, _)| area.kinds.contains(kind));
let succeeded = of_area.clone().filter(|(_, ok)| *ok).count();
let refused = of_area.filter(|(_, ok)| !*ok).count();
(*area, succeeded, refused)
})
.collect()
}
#[must_use]
pub fn has_real_sign_in(cases: &[&Case]) -> bool {
cases.iter().any(|case| {
case.steps.iter().any(|step| {
matches!(
step,
crate::cli_chains::action::Step::Run(Action::AuthLogin)
)
})
})
}