fixture_manager/
summaries.rs1use crate::context::ContextImpl;
5use anyhow::Result;
6use camino::{Utf8Path, Utf8PathBuf};
7use fixtures::json::JsonFixture;
8use guppy::graph::{
9 cargo::CargoSet,
10 summaries::{CargoSetInputsSummary, Summary, diff::SummaryDiff},
11};
12use guppy_cmdlib::PackagesAndFeatures;
13use hakari::diffy::{PatchFormatter, create_patch};
14use once_cell::sync::Lazy;
15use proptest_ext::ValueGenerator;
16use std::fmt::Write;
17
18pub struct SummaryContext;
19
20pub struct ExistingSummary {
21 summary: Summary,
22 contents: String,
23}
24
25impl<'g> ContextImpl<'g> for SummaryContext {
26 type IterArgs = usize;
27 type IterItem = (usize, Summary);
28 type Existing = ExistingSummary;
29
30 fn dir_name(fixture: &'g JsonFixture) -> Utf8PathBuf {
31 fixture
32 .abs_path()
33 .parent()
34 .expect("up to dirname of summary")
35 .join("summaries")
36 }
37
38 fn file_name(fixture: &'g JsonFixture, &(count, _): &Self::IterItem) -> String {
39 format!("{}-{}.toml", fixture.name(), count)
40 }
41
42 fn iter(
43 fixture: &'g JsonFixture,
44 &count: &Self::IterArgs,
45 ) -> Box<dyn Iterator<Item = Self::IterItem> + 'g> {
46 let mut generator = ValueGenerator::from_seed(fixture.name());
49
50 let graph = fixture.graph();
51
52 let packages_features_strategy = PackagesAndFeatures::strategy(graph);
53 let cargo_opts_strategy = graph.proptest1_cargo_options_strategy();
54
55 let iter = (0..count).map(move |idx| {
56 let mut iter_generator = generator.partial_clone();
59
60 let packages_features = iter_generator
61 .partial_clone()
62 .generate(&packages_features_strategy);
63 let (initials, features_only) = packages_features
64 .make_feature_sets(graph)
65 .expect("valid feature set");
66
67 let cargo_opts = iter_generator
68 .partial_clone()
69 .generate(&cargo_opts_strategy);
70 let cargo_set = CargoSet::new(initials, features_only, &cargo_opts)
71 .expect("into_cargo_set succeeded");
72 let summary = cargo_set
73 .to_summary()
74 .expect("generated summaries should serialize correctly");
75
76 let metadata: CargoSetInputsSummary = summary
77 .metadata
78 .clone()
79 .try_into()
80 .expect("metadata deserialized as a CargoSetInputsSummary");
81 let inputs = metadata
82 .to_cargo_set_inputs(graph)
83 .expect("cargo set inputs rebuilt from the summary");
84 assert_eq!(
85 &inputs.features_only,
86 &cargo_set.inputs().features_only,
87 "features-only set rebuilt from the summary",
88 );
89 let rebuilt_summary = inputs
90 .to_cargo_set(cargo_set.initials().clone())
91 .expect("cargo set rebuilt from the summary")
92 .to_summary()
93 .expect("rebuilt summary generated");
94 assert_eq!(
95 rebuilt_summary, summary,
96 "resolution rebuilt from the summary matches the original",
97 );
98
99 (idx, summary)
100 });
101
102 Box::new(iter)
103 }
104
105 fn parse_existing(_: &Utf8Path, contents: String) -> Result<Self::Existing> {
106 let summary = Summary::parse(&contents)?;
107 Ok(ExistingSummary { summary, contents })
108 }
109
110 fn is_changed(
111 fixture: &'g JsonFixture,
112 item: &Self::IterItem,
113 existing: &Self::Existing,
114 ) -> Result<bool> {
115 let mut rendered = String::new();
116 Self::write_to_string(fixture, item, &mut rendered)?;
117 Ok(existing.contents != rendered)
118 }
119
120 fn diff(
121 fixture: &'g JsonFixture,
122 item @ (_, summary): &Self::IterItem,
123 existing: Option<&Self::Existing>,
124 ) -> String {
125 static EMPTY_SUMMARY: Lazy<Summary> = Lazy::new(Summary::default);
127
128 let existing_summary = match existing {
129 Some(existing) => &existing.summary,
130 None => &*EMPTY_SUMMARY,
131 };
132
133 let diff = SummaryDiff::new(existing_summary, summary);
134 if diff.is_changed() {
135 return format!("{}", diff.report());
136 }
137
138 let existing_contents = existing.map_or("", |existing| existing.contents.as_str());
139 let mut rendered = String::new();
140 match Self::write_to_string(fixture, item, &mut rendered) {
141 Ok(()) => {
142 let patch = create_patch(existing_contents, &rendered);
143 format!("{}", PatchFormatter::new().fmt_patch(&patch))
144 }
145 Err(err) => format!("error while rendering summary: {err}"),
146 }
147 }
148
149 fn write_to_string(
150 fixture: &'g JsonFixture,
151 (_, summary): &Self::IterItem,
152 out: &mut String,
153 ) -> Result<()> {
154 writeln!(
155 out,
156 "# This summary was @generated. To regenerate, run:\n\
157 # cargo run -p fixture-manager -- generate-summaries --fixture {}\n",
158 fixture.name()
159 )?;
160
161 summary.write_to_string(out)?;
162 Ok(())
163 }
164}