1use std::collections::BTreeMap;
8
9use serde::{Deserialize, Serialize};
10
11use super::{AlreadyPresent, AlreadyPresentSkipped, DepPinDivergence, PinBump, Unanalyzed};
12use crate::model::names::{
13 CommitSha, ModuleName, ProjectName, RecordName, RelativePath, vocabulary,
14};
15
16#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
20pub struct Diagnostics {
21 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
22 pub untracked_calls: BTreeMap<ModuleName, usize>,
23 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
24 pub untracked_records: BTreeMap<RecordName, usize>,
25 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
28 pub context_refs_missing: BTreeMap<ModuleName, usize>,
29 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
33 pub unattributed_paths: BTreeMap<String, usize>,
34 #[serde(default, skip_serializing_if = "Unanalyzed::is_empty")]
35 pub unanalyzed: Unanalyzed,
36 #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub suggested_suites: Vec<String>,
41 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
51 pub missing_test_modules: BTreeMap<RelativePath, BTreeMap<ModuleName, usize>>,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub already_present: Option<AlreadyPresent>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub already_present_skipped: Option<AlreadyPresentSkipped>,
59 #[serde(default, skip_serializing_if = "Vec::is_empty")]
62 pub dep_pin_divergence: Vec<DepPinDivergence>,
63 #[serde(default, skip_serializing_if = "Vec::is_empty")]
66 pub pin_bumps: Vec<PinBump>,
67 #[serde(default, skip_serializing_if = "ShapeCheckTally::is_empty")]
70 pub qualified_call_shape_checks: ShapeCheckTally,
71 #[serde(default, skip_serializing_if = "ShapeCheckTally::is_empty")]
75 pub local_call_shape_checks: ShapeCheckTally,
76 #[serde(default, skip_serializing_if = "MacroValueTally::is_empty")]
79 pub macro_value_checks: MacroValueTally,
80 #[serde(default, skip_serializing_if = "IndirectCallTally::is_empty")]
84 pub indirect_call_checks: IndirectCallTally,
85 #[serde(default, skip_serializing_if = "Vec::is_empty")]
91 pub dormant_detectors: Vec<DormantDetector>,
92}
93
94impl Diagnostics {
95 pub fn is_empty(&self) -> bool {
96 let Self {
98 untracked_calls,
99 untracked_records,
100 context_refs_missing,
101 unattributed_paths,
102 unanalyzed,
103 suggested_suites,
104 missing_test_modules,
105 already_present,
106 already_present_skipped,
107 dep_pin_divergence,
108 pin_bumps,
109 qualified_call_shape_checks,
110 local_call_shape_checks,
111 macro_value_checks,
112 indirect_call_checks,
113 dormant_detectors,
114 } = self;
115 untracked_calls.is_empty()
116 && untracked_records.is_empty()
117 && context_refs_missing.is_empty()
118 && unattributed_paths.is_empty()
119 && unanalyzed.is_empty()
120 && suggested_suites.is_empty()
121 && missing_test_modules.is_empty()
122 && already_present.is_none()
123 && already_present_skipped.is_none()
124 && dep_pin_divergence.is_empty()
125 && pin_bumps.is_empty()
126 && qualified_call_shape_checks.is_empty()
127 && local_call_shape_checks.is_empty()
128 && macro_value_checks.is_empty()
129 && indirect_call_checks.is_empty()
130 && dormant_detectors.is_empty()
131 }
132
133 pub fn record_missing_test_module(&mut self, suite: RelativePath, helper: ModuleName) {
136 let inner = self.missing_test_modules.entry(suite).or_default();
137 *inner.entry(helper).or_insert(0) += 1;
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
144#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
145#[serde(rename_all = "snake_case")]
146pub enum TargetMatchKind {
147 TrailerIntersection,
150 TrailerAncestry,
153 PatchId,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
163pub struct TargetMatch {
164 pub commit: CommitSha,
165 pub via: TargetMatchKind,
166 pub subject: String,
167}
168
169#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
174#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
175pub struct ShapeCheckTally {
176 #[serde(default, skip_serializing_if = "is_zero")]
180 pub compared: usize,
181 #[serde(default, skip_serializing_if = "is_zero")]
183 pub withheld_no_spec: usize,
184 #[serde(default, skip_serializing_if = "is_zero")]
187 pub withheld_unknown_type: usize,
188 #[serde(default, skip_serializing_if = "is_zero")]
193 pub withheld_no_source: usize,
194 #[serde(default, skip_serializing_if = "is_zero")]
198 pub withheld_imported: usize,
199}
200
201#[allow(clippy::trivially_copy_pass_by_ref)]
202fn is_zero(n: &usize) -> bool {
203 *n == 0
204}
205
206impl ShapeCheckTally {
207 pub fn is_empty(&self) -> bool {
208 *self == Self::default()
209 }
210
211 pub fn merge(&mut self, other: Self) {
214 self.compared += other.compared;
215 self.withheld_no_spec += other.withheld_no_spec;
216 self.withheld_unknown_type += other.withheld_unknown_type;
217 self.withheld_no_source += other.withheld_no_source;
218 self.withheld_imported += other.withheld_imported;
219 }
220}
221
222#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
226#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
227pub struct MacroValueTally {
228 #[serde(default, skip_serializing_if = "is_zero")]
231 pub compared: usize,
232 #[serde(default, skip_serializing_if = "is_zero")]
235 pub withheld_definition_elsewhere: usize,
236 #[serde(default, skip_serializing_if = "is_zero")]
239 pub withheld_multiple_defines: usize,
240 #[serde(default, skip_serializing_if = "is_zero")]
242 pub withheld_no_source: usize,
243}
244
245impl MacroValueTally {
246 pub fn is_empty(&self) -> bool {
247 *self == Self::default()
248 }
249
250 pub fn merge(&mut self, other: Self) {
253 self.compared += other.compared;
254 self.withheld_definition_elsewhere += other.withheld_definition_elsewhere;
255 self.withheld_multiple_defines += other.withheld_multiple_defines;
256 self.withheld_no_source += other.withheld_no_source;
257 }
258}
259
260#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
265#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
266pub struct IndirectCallTally {
267 #[serde(default, skip_serializing_if = "is_zero")]
270 pub checked: usize,
271 #[serde(default, skip_serializing_if = "is_zero")]
275 pub withheld_dynamic: usize,
276}
277
278impl IndirectCallTally {
279 pub fn is_empty(&self) -> bool {
280 *self == Self::default()
281 }
282
283 pub fn merge(&mut self, other: Self) {
286 self.checked += other.checked;
287 self.withheld_dynamic += other.withheld_dynamic;
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
294#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
295pub struct DormantDetector {
296 pub detector: DetectorName,
297 pub missing_input: MissingInput,
298}
299
300vocabulary!(
301 pub enum DetectorName: "detector name" {
303 DepBehaviourConformance => "dep_behaviour_conformance",
304 OptionKeyDrift => "option_key_drift",
305 MacroUndefinedOnTarget => "macro_undefined_on_target",
306 SuiteNotRegisteredForCt => "suite_not_registered_for_ct",
307 SchemaFeatureUnsupportedOnPin => "schema_feature_unsupported_on_pin",
308 SchemaKeyReaderMissing => "schema_key_reader_missing",
309 }
310);
311
312vocabulary!(
313 pub enum MissingInput: "missing input" {
315 TargetRepoDirPath => "target_repo_dir_path",
316 SourcePin => "source_pin",
317 CuttlefishPin => "cuttlefish_pin",
318 }
319);
320
321#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
323#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
324pub struct HunkTally {
325 pub applied: usize,
326 pub considered: usize,
327 pub ambiguous: usize,
328 pub low_confidence: usize,
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
335#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
336pub struct ContentPresence {
337 pub pin: ProjectName,
341 pub hunks_already_applied: usize,
342 pub hunks_considered: usize,
343 pub hunks_ambiguous: usize,
344 pub hunks_low_confidence: usize,
345 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
346 pub per_file: BTreeMap<RelativePath, HunkTally>,
347}