use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::model::batch::BatchResult;
use crate::model::eval::{BuildOutcome, CorpusEntry, PredictedConflict};
use crate::model::names::{MacroName, ModuleName, ProjectName, RelativePath, vocabulary};
use crate::model::pin::Pin;
use crate::model::resolver_coverage::ResolverCoverage;
use crate::model::summary::VerdictKind;
use crate::model::verdict::{
ApplyConflictKind, Diagnostics, IncludeDirective, PinVerdict, Reason, SeriesEvaluation,
SeriesVerdict, SnapshotSide, TargetAxisSlot, TestCallSite, Verdict, non_self_tracked,
};
vocabulary!(
#[non_exhaustive]
#[derive(Hash)]
pub enum AggregateVerdict: "aggregate verdict" {
Compatible => "compatible",
RequiresAdaptation => "requires_adaptation",
Incompatible => "incompatible",
Inapplicable => "inapplicable",
Empty => "empty",
}
);
impl AggregateVerdict {
#[must_use]
pub fn flagged(self) -> bool {
matches!(self, Self::Incompatible | Self::RequiresAdaptation)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SeriesEvaluationView<'a> {
results: &'a SeriesVerdict,
diagnostics: &'a Diagnostics,
}
impl<'a> SeriesEvaluationView<'a> {
#[must_use]
pub fn new(results: &'a SeriesVerdict, diagnostics: &'a Diagnostics) -> Self {
Self {
results,
diagnostics,
}
}
#[must_use]
pub fn worst_verdict(self) -> AggregateVerdict {
if self.results.results.is_empty() {
return AggregateVerdict::Empty;
}
let s = &self.results.summary;
if s.incompatible > 0 {
AggregateVerdict::Incompatible
} else if s.requires_adaptation > 0 {
AggregateVerdict::RequiresAdaptation
} else if s.compatible > 0 {
AggregateVerdict::Compatible
} else if s.inapplicable > 0 {
AggregateVerdict::Inapplicable
} else {
AggregateVerdict::Empty
}
}
#[must_use]
pub fn has_blocking_reason(self) -> bool {
self.results
.results
.iter()
.any(|p| matches!(&p.verdict, Verdict::Incompatible { .. }))
}
pub fn pins_in(self, verdict: VerdictKind) -> impl Iterator<Item = &'a PinVerdict> {
self.results
.results
.iter()
.filter(move |p| VerdictKind::from(&p.verdict) == verdict)
}
pub fn reasons(self) -> impl Iterator<Item = &'a Reason> {
self.results.results.iter().flat_map(pin_reasons)
}
pub fn reasons_for(self, pin: &Pin) -> impl Iterator<Item = &'a Reason> {
self.results
.results
.iter()
.filter(move |p| &p.pin == pin)
.flat_map(pin_reasons)
}
#[must_use]
pub fn tracked_refs(self, self_projects: &BTreeSet<ProjectName>) -> u32 {
non_self_tracked(&self.results.results, self_projects)
}
#[must_use]
pub fn pin_by_project(self, project: &ProjectName) -> Option<&'a PinVerdict> {
self.results
.results
.iter()
.find(|p| &p.pin.project == project)
}
#[must_use]
pub fn missing_test_modules(self) -> &'a BTreeMap<RelativePath, BTreeMap<ModuleName, usize>> {
&self.diagnostics.missing_test_modules
}
fn pin_reason_pairs(self) -> impl Iterator<Item = (&'a PinVerdict, &'a Reason)> {
self.results
.results
.iter()
.flat_map(|pin| pin_reasons(pin).map(move |r| (pin, r)))
}
pub fn test_module_symbol_missing(
self,
) -> impl Iterator<Item = TestModuleSymbolMissingFinding<'a>> {
self.pin_reason_pairs().filter_map(|(pin, r)| match r {
Reason::TestModuleSymbolMissing {
suite_path,
missing_module,
call_sites,
} => Some(TestModuleSymbolMissingFinding {
pin,
suite_path,
missing_module,
call_sites: call_sites.as_slice(),
}),
_ => None,
})
}
pub fn header_file_missing(self) -> impl Iterator<Item = HeaderFileMissingFinding<'a>> {
self.pin_reason_pairs().filter_map(|(pin, r)| match r {
Reason::HeaderFileMissing {
source_path,
include_directive,
attempted_paths,
} => Some(HeaderFileMissingFinding {
pin,
source_path,
include_directive,
attempted_paths: attempted_paths.as_slice(),
}),
_ => None,
})
}
pub fn behaviour_module_missing(
self,
) -> impl Iterator<Item = BehaviourModuleMissingFinding<'a>> {
self.pin_reason_pairs().filter_map(|(pin, r)| match r {
Reason::BehaviourModuleMissing {
source_path,
behaviour,
} => Some(BehaviourModuleMissingFinding {
pin,
source_path,
behaviour,
}),
_ => None,
})
}
pub fn versioned_machine_snapshot_missing(
self,
) -> impl Iterator<Item = VersionedMachineSnapshotMissingFinding<'a>> {
self.pin_reason_pairs().filter_map(|(pin, r)| match r {
Reason::VersionedMachineSnapshotMissing { module, side } => {
Some(VersionedMachineSnapshotMissingFinding {
pin,
module,
side: *side,
})
}
_ => None,
})
}
pub fn wire_constant_bindings_missing(
self,
) -> impl Iterator<Item = WireConstantBindingsMissingFinding<'a>> {
self.pin_reason_pairs().filter_map(|(pin, r)| match r {
Reason::WireConstantBindingsMissing {
module,
macros,
side,
} => Some(WireConstantBindingsMissingFinding {
pin,
module,
macros: macros.as_slice(),
side: *side,
}),
_ => None,
})
}
}
impl From<BatchResult> for SeriesEvaluation {
fn from(row: BatchResult) -> Self {
Self {
verdict: row.verdict,
diagnostics: row.diagnostics,
patch_facts: row.patch_facts,
touched_paths: row.touched_paths,
pr_commits: row.pr_commits,
target: TargetAxisSlot::from_wire_pair(row.apply, row.target_findings),
}
}
}
impl BatchResult {
#[must_use]
pub fn evaluation(&self) -> SeriesEvaluationView<'_> {
SeriesEvaluationView::new(&self.verdict, &self.diagnostics)
}
#[must_use]
pub fn to_corpus_entry(
&self,
observed: BuildOutcome,
coverage: Option<&ResolverCoverage>,
) -> Option<CorpusEntry> {
let fingerprint = self.verdict_fingerprint.clone()?;
let verdict = self.evaluation().worst_verdict();
if verdict == AggregateVerdict::Empty {
return None;
}
Some(CorpusEntry {
fingerprint,
verdict,
outcome: observed,
coverage: coverage.cloned(),
})
}
#[must_use]
pub fn predicted_conflicts(&self) -> Vec<PredictedConflict> {
let mut by_path: BTreeMap<PathBuf, ApplyConflictKind> = BTreeMap::new();
let mut record = |path: PathBuf, kind: ApplyConflictKind| {
by_path
.entry(path)
.and_modify(|seen| *seen = (*seen).max(kind))
.or_insert(kind);
};
for (path, kind) in self
.evaluation()
.reasons()
.filter_map(Reason::apply_conflict)
{
record(path.to_path_buf(), kind);
}
if let Some(forecast) = &self.apply {
for (path, kind) in forecast.conflicts() {
record(PathBuf::from(path.as_str()), kind);
}
}
by_path
.into_iter()
.map(|(path, kind)| PredictedConflict { path, kind })
.collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct TestModuleSymbolMissingFinding<'a> {
pub pin: &'a PinVerdict,
pub suite_path: &'a RelativePath,
pub missing_module: &'a ModuleName,
pub call_sites: &'a [TestCallSite],
}
#[derive(Debug, Clone, Copy)]
pub struct HeaderFileMissingFinding<'a> {
pub pin: &'a PinVerdict,
pub source_path: &'a RelativePath,
pub include_directive: &'a IncludeDirective,
pub attempted_paths: &'a [RelativePath],
}
#[derive(Debug, Clone, Copy)]
pub struct BehaviourModuleMissingFinding<'a> {
pub pin: &'a PinVerdict,
pub source_path: &'a RelativePath,
pub behaviour: &'a ModuleName,
}
#[derive(Debug, Clone, Copy)]
pub struct VersionedMachineSnapshotMissingFinding<'a> {
pub pin: &'a PinVerdict,
pub module: &'a ModuleName,
pub side: SnapshotSide,
}
#[derive(Debug, Clone, Copy)]
pub struct WireConstantBindingsMissingFinding<'a> {
pub pin: &'a PinVerdict,
pub module: &'a ModuleName,
pub macros: &'a [MacroName],
pub side: SnapshotSide,
}
fn pin_reasons(pin: &PinVerdict) -> impl Iterator<Item = &Reason> {
pin.verdict.reasons().iter()
}