use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::surface_graph::{ApplicationSurfaceGraph, ApplicationSurfaceKind};
use crate::{CoverageMeasurement, NodeCoverage};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SurfaceCoverageState {
MeasuredCovered,
MeasuredPartial,
MeasuredUncovered,
Unmeasured,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SurfaceCoverage {
pub surface: String,
pub kind: ApplicationSurfaceKind,
pub state: SurfaceCoverageState,
pub covered_nodes: u64,
pub uncovered_nodes: u64,
pub unmeasured_nodes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CoverageAutopilot {
pub surfaces: Vec<SurfaceCoverage>,
pub unmeasured: Vec<String>,
pub uncovered: Vec<String>,
pub partial: Vec<String>,
pub covered: Vec<String>,
pub truncated: bool,
}
#[must_use]
pub fn coverage_autopilot(
graph: &ApplicationSurfaceGraph,
coverage: &[NodeCoverage],
) -> CoverageAutopilot {
let by_node = coverage
.iter()
.map(|item| (item.node_id.as_str(), item.measurement))
.collect::<BTreeMap<_, _>>();
let mut out = CoverageAutopilot {
truncated: graph.truncated,
..CoverageAutopilot::default()
};
for surface in &graph.surfaces {
let mut covered = 0_u64;
let mut uncovered = 0_u64;
let mut unmeasured = 0_u64;
if surface.implementation_nodes.is_empty() {
unmeasured = 1;
}
for node in &surface.implementation_nodes {
match by_node.get(node.as_str()).copied() {
Some(CoverageMeasurement::Covered) => covered = covered.saturating_add(1),
Some(CoverageMeasurement::Uncovered) => uncovered = uncovered.saturating_add(1),
Some(CoverageMeasurement::Unmeasured) | None => {
unmeasured = unmeasured.saturating_add(1);
}
}
}
let state = if covered > 0 && uncovered == 0 && unmeasured == 0 {
SurfaceCoverageState::MeasuredCovered
} else if covered > 0 {
SurfaceCoverageState::MeasuredPartial
} else if uncovered > 0 && unmeasured == 0 {
SurfaceCoverageState::MeasuredUncovered
} else {
SurfaceCoverageState::Unmeasured
};
let reading = SurfaceCoverage {
surface: surface.id.clone(),
kind: surface.kind,
state,
covered_nodes: covered,
uncovered_nodes: uncovered,
unmeasured_nodes: unmeasured,
};
match state {
SurfaceCoverageState::MeasuredCovered => out.covered.push(surface.id.clone()),
SurfaceCoverageState::MeasuredPartial => out.partial.push(surface.id.clone()),
SurfaceCoverageState::MeasuredUncovered => out.uncovered.push(surface.id.clone()),
SurfaceCoverageState::Unmeasured => out.unmeasured.push(surface.id.clone()),
}
out.surfaces.push(reading);
}
out.surfaces.sort_by(|left, right| left.surface.cmp(&right.surface));
out.covered.sort();
out.partial.sort();
out.uncovered.sort();
out.unmeasured.sort();
out
}