use crate::error::{PassengerError, Result};
use crate::gate::EditSet;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct TestRun {
pub code: Option<i32>,
pub results: Vec<String>,
pub failing: Vec<String>,
pub tail: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Compared {
pub exit: Option<i32>,
pub results: Vec<String>,
pub failing_at_base: Vec<String>,
pub new_failures: Vec<String>,
}
impl Compared {
pub fn ok(&self) -> bool {
self.exit == Some(0) || (self.exit.is_some() && self.new_failures.is_empty())
}
}
pub const PROOF_ENV: &[(&str, &str)] = custodian_kit::proof_place::PROOF_ENV;
pub const PROOF_DIR_ENV: &str = custodian_kit::proof_place::PROOF_DIR_ENV;
pub struct ProofDir {
place: custodian_kit::proof_place::ProofPlace,
names: std::collections::BTreeMap<String, String>,
}
impl ProofDir {
pub fn take(workspace: &Path) -> Result<Self> {
Ok(ProofDir { place: custodian_kit::proof_place::ProofPlace::take(workspace)?, names: crate::cargo::cargo_names(workspace) })
}
pub fn cargo_name(&self, package: &str) -> String {
self.names.get(package).or_else(|| self.names.get(&package.replace('-', "_"))).cloned().unwrap_or_else(|| package.to_string())
}
pub fn prune_incremental(&self) -> u64 {
self.place.prune_incremental()
}
pub fn root(&self) -> &Path {
self.place.root()
}
pub fn target(&self) -> &Path {
self.place.target()
}
pub fn sync(&self) -> Result<()> {
Ok(self.place.sync()?)
}
pub fn write(&self, edits: &EditSet) -> Result<Vec<PathBuf>> {
edits.write_to(self.place.workspace(), self.place.root())
}
pub fn expand_fns(&self, package: &str, items: &[String]) -> Result<(BTreeMap<String, Option<String>>, String, i64)> {
use custodian_kit::exec::{line, Cargo, Plan, Watch};
let l = Cargo::expand(self.root().join("Cargo.toml").as_os_str(), self.cargo_name(package)).lib().ugly();
let mut ln = line("expand", &l).map_err(|e| PassengerError::TransformRefused { detail: e.to_string() })?;
ln.env.extend(self.place.env());
let shown = format!("cargo expand -p {} --lib --ugly", self.cargo_name(package));
let walked = Plan::new("proof-expand").then(ln).walk(Watch::Quiet, |_, _| {}).map_err(|e| PassengerError::TransformRefused { detail: e.to_string() })?;
let w = walked.into_iter().next().ok_or_else(|| PassengerError::TransformRefused { detail: "cargo expand did not run".into() })?;
if w.code != Some(0) {
let tail = w.stderr.lines().rev().take(12).collect::<Vec<_>>().into_iter().rev().collect::<Vec<_>>().join("\n");
return Err(PassengerError::TransformRefused { detail: format!("cargo expand -p {package}: exit {:?}: {tail}", w.code) });
}
let file = syn::parse_file(&w.stdout).map_err(|e| PassengerError::TransformRefused { detail: format!("cargo expand's output for {package} does not parse: {e}") })?;
let found = items.iter().map(|item| (item.clone(), crate::to_toolkit::fn_in(&file.items, package, item).map(|f| custodian_kit::toolkit::normal::proof_form(&f)))).collect();
Ok((found, shown, w.millis))
}
pub fn test(&self, package: &str) -> Result<TestRun> {
use custodian_kit::exec::{line, Cargo, Plan, Watch};
let l = Cargo::test(self.root().join("Cargo.toml").as_os_str(), self.cargo_name(package));
let mut ln = line("tests", &l).map_err(|e| PassengerError::TransformRefused { detail: e.to_string() })?.going_on();
ln.env.extend(self.place.env());
let walked = Plan::new("proof-tests").then(ln).walk(Watch::Quiet, |_, _| {}).map_err(|e| PassengerError::TransformRefused { detail: e.to_string() })?;
let w = walked.into_iter().next().ok_or_else(|| PassengerError::TransformRefused { detail: "cargo test did not run".into() })?;
let results = w.stdout.lines().chain(w.stderr.lines()).filter(|l| l.starts_with("test result:")).map(str::to_string).collect();
let failing = w.stdout.lines().filter_map(|l| l.strip_prefix("test ").and_then(|r| r.strip_suffix(" ... FAILED"))).map(str::to_string).collect();
let tail = w.stderr.lines().rev().find(|l| !l.trim().is_empty()).unwrap_or_default().to_string();
Ok(TestRun { code: w.code, results, failing, tail })
}
pub fn tests_compared(&self, package: &str, edits: &EditSet) -> Result<Compared> {
self.write(edits)?;
let after = self.test(package)?;
if after.code == Some(0) {
return Ok(Compared { exit: after.code, results: after.results, failing_at_base: Vec::new(), new_failures: Vec::new() });
}
if after.failing.is_empty() {
let why = format!("the tests did not run to a result: {}", after.tail);
return Ok(Compared { exit: after.code, results: after.results, failing_at_base: Vec::new(), new_failures: vec![why] });
}
self.sync()?;
let base = self.test(package);
self.write(edits)?;
let base = base?;
let at_base: BTreeSet<&String> = base.failing.iter().collect();
let (same, new): (Vec<String>, Vec<String>) = after.failing.into_iter().partition(|t| at_base.contains(t));
Ok(Compared { exit: after.code, results: after.results, failing_at_base: same, new_failures: new })
}
pub fn in_copy(&self, path: &Path) -> Result<PathBuf> {
Ok(self.root().join(crate::gate::relative(self.place.workspace(), path)?))
}
}