cargo_coverage_gate/
error.rs1use serde_json::Value;
18
19#[ohno::error]
30#[from(
31 LoadMetadataError,
32 InvalidThresholdValueError,
33 ThresholdOutOfRangeError,
34 InvalidNoCoverableLinesValueError,
35 ConflictingCoverageMetadataError,
36 WorkspaceTargetPolicyError,
37 InvalidTargetTableError,
38 InvalidTargetPolicyShapeError,
39 MissingTargetPolicyBehaviorError,
40 InvalidTargetSelectorError,
41 UnsupportedTargetSelectorError,
42 AmbiguousTargetPolicyError,
43 WorkspaceScopedNoCoverableLinesError,
44 ResolveTargetError,
45 ParseLcovError,
46 ReadLcovError,
47 UnknownPackageSelectorError
48)]
49pub struct CoverageGateError;
50
51#[ohno::error]
53#[display("failed to load workspace metadata")]
54#[from(cargo_metadata::Error)]
55pub(crate) struct LoadMetadataError;
56
57#[ohno::error]
60#[display("{source}: `coverage-gate.min-lines-percent` must be a number, got {min}")]
61pub(crate) struct InvalidThresholdValueError {
62 pub source: String,
63 pub min: Value,
64}
65
66#[ohno::error]
69#[display(
70 "invalid coverage-gate min-lines-percent value `{value}` for {source}: \
71 expected a value in {lower:.1}..={upper:.1}"
72)]
73pub(crate) struct ThresholdOutOfRangeError {
74 pub source: String,
75 pub value: f64,
76 pub lower: f64,
77 pub upper: f64,
78}
79
80#[ohno::error]
83#[display("{source}: `coverage-gate.expect-no-coverable-lines` must be a boolean, got {value}")]
84pub(crate) struct InvalidNoCoverableLinesValueError {
85 pub source: String,
86 pub value: Value,
87}
88
89#[ohno::error]
95#[display(
96 "{source}: `coverage-gate` cannot set both `min-lines-percent` and \
97 `expect-no-coverable-lines`; pick one"
98)]
99pub(crate) struct ConflictingCoverageMetadataError {
100 pub source: String,
101}
102
103#[ohno::error]
107#[display(
108 "`coverage-gate.expect-no-coverable-lines` is a package-level assertion and \
109 cannot be set in `[workspace.metadata.coverage-gate]`"
110)]
111pub(crate) struct WorkspaceScopedNoCoverableLinesError;
112
113#[ohno::error]
115#[display("coverage-gate target policies are package-scoped and cannot be set in workspace metadata")]
116pub(crate) struct WorkspaceTargetPolicyError;
117
118#[ohno::error]
120#[display("{source}: coverage-gate `target` must be a table keyed by target triple or cfg expression")]
121pub(crate) struct InvalidTargetTableError {
122 pub(crate) source: String,
123}
124
125#[ohno::error]
127#[display("{source}: coverage-gate target policy must be a table")]
128pub(crate) struct InvalidTargetPolicyShapeError {
129 pub(crate) source: String,
130}
131
132#[ohno::error]
134#[display("{source}: target policy must set `min-lines-percent` or `expect-no-coverable-lines = true`")]
135pub(crate) struct MissingTargetPolicyBehaviorError {
136 pub(crate) source: String,
137}
138
139#[ohno::error]
141#[display("{source}: invalid coverage-gate target selector `{selector}`")]
142#[from(cargo_platform::ParseError)]
143pub(crate) struct InvalidTargetSelectorError {
144 pub(crate) source: String,
145 pub(crate) selector: String,
146}
147
148#[ohno::error]
150#[display("{source}: coverage-gate target selector `{selector}` uses unsupported build-context configuration options: {options}")]
151pub(crate) struct UnsupportedTargetSelectorError {
152 pub(crate) source: String,
153 pub(crate) selector: String,
154 pub(crate) options: String,
155}
156
157#[ohno::error]
159#[display(
160 "{source}: multiple coverage-gate target policies match `{target}`: {selectors}; \
161 use disjoint cfg expressions or an exact Rust target-triple override"
162)]
163pub(crate) struct AmbiguousTargetPolicyError {
164 pub(crate) source: String,
165 pub(crate) target: String,
166 pub(crate) selectors: String,
167}
168
169#[ohno::error]
171#[display("failed to resolve Rust target")]
172#[from(ExecuteRustcError, RustcCommandFailedError, MissingRustcHostTargetError, InvalidRustcCfgError)]
173pub(crate) struct ResolveTargetError;
174
175#[ohno::error]
177#[display("could not execute `{command}`")]
178#[from(std::io::Error)]
179pub(crate) struct ExecuteRustcError {
180 pub(crate) command: String,
181}
182
183#[ohno::error]
185#[display("`{command}` exited with {status}: {stderr}")]
186pub(crate) struct RustcCommandFailedError {
187 pub(crate) command: String,
188 pub(crate) status: String,
189 pub(crate) stderr: String,
190}
191
192#[ohno::error]
194#[display("`{command}` did not report a host target")]
195pub(crate) struct MissingRustcHostTargetError {
196 pub(crate) command: String,
197}
198
199#[ohno::error]
201#[display("rustc reported invalid cfg `{value}` for Rust target `{target}`")]
202#[from(cargo_platform::ParseError)]
203pub(crate) struct InvalidRustcCfgError {
204 pub(crate) value: String,
205 pub(crate) target: String,
206}
207
208#[ohno::error]
210#[display("lcov tracefile is not well-formed")]
211#[from(lcov::report::ParseError)]
212pub(crate) struct ParseLcovError;
213
214#[ohno::error]
217#[display("failed to read lcov tracefile `{path}`")]
218pub(crate) struct ReadLcovError {
219 pub path: String,
220}
221
222#[ohno::error]
224#[display("`--package` selector `{selector}` did not match any workspace member")]
225pub(crate) struct UnknownPackageSelectorError {
226 pub selector: String,
227}
228
229#[cfg(test)]
230#[cfg_attr(coverage_nightly, coverage(off))]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn umbrella_propagates_load_metadata_chain() {
236 let inner = LoadMetadataError::caused_by(std::io::Error::other("no manifest"));
237 let outer: CoverageGateError = inner.into();
238 let rendered = outer.to_string();
239 assert!(rendered.contains("failed to load workspace metadata"));
240 assert!(rendered.contains("no manifest"));
241 }
242
243 #[test]
244 fn umbrella_propagates_parse_lcov() {
245 let inner = ParseLcovError::new();
246 let outer: CoverageGateError = inner.into();
247 let rendered = outer.to_string();
248 assert!(rendered.contains("lcov tracefile"));
249 }
250
251 #[test]
252 fn execute_rustc_error_preserves_io_source() {
253 let error = ExecuteRustcError::caused_by("rustc -vV".to_owned(), std::io::Error::other("launch failed"));
254 let source = std::error::Error::source(&error).expect("execute error must retain its IO source");
255 assert_eq!(source.to_string(), "launch failed");
256 }
257
258 #[test]
259 fn unknown_package_selector_carries_pattern() {
260 let err = UnknownPackageSelectorError::new("nope-*".to_owned());
261 let rendered = err.to_string();
262 assert!(rendered.contains("nope-*"));
263 assert!(rendered.contains("did not match"));
264 }
265
266 #[test]
267 fn threshold_out_of_range_renders_value_and_bounds() {
268 let err = ThresholdOutOfRangeError::new("alpha".to_owned(), 150.0, 0.0, 100.0);
269 let rendered = err.to_string();
270 assert!(rendered.contains("150"));
271 assert!(rendered.contains("alpha"));
272 assert!(rendered.contains("0.0..=100.0"));
273 }
274
275 #[test]
276 fn invalid_no_coverable_lines_value_renders_source_and_value() {
277 let err = InvalidNoCoverableLinesValueError::new("alpha".to_owned(), Value::from("yes"));
278 let rendered = err.to_string();
279 assert!(rendered.contains("alpha"));
280 assert!(rendered.contains("expect-no-coverable-lines"));
281 assert!(rendered.contains("boolean"));
282 assert!(rendered.contains("yes"));
283 }
284
285 #[test]
286 fn conflicting_coverage_metadata_renders_source() {
287 let err = ConflictingCoverageMetadataError::new("alpha".to_owned());
288 let rendered = err.to_string();
289 assert!(rendered.contains("alpha"));
290 assert!(rendered.contains("min-lines-percent"));
291 assert!(rendered.contains("expect-no-coverable-lines"));
292 }
293
294 #[test]
295 fn workspace_scoped_no_coverable_lines_mentions_workspace() {
296 let err = WorkspaceScopedNoCoverableLinesError::new();
297 let rendered = err.to_string();
298 assert!(rendered.contains("expect-no-coverable-lines"));
299 assert!(rendered.contains("workspace.metadata.coverage-gate"));
300 }
301
302 #[test]
303 fn ambiguous_target_policy_names_target_and_selectors() {
304 let err = AmbiguousTargetPolicyError::new(
305 "alpha".to_owned(),
306 "x86_64-pc-windows-msvc".to_owned(),
307 "cfg(windows), cfg(target_os = \"windows\")".to_owned(),
308 );
309 let rendered = err.to_string();
310 assert!(rendered.contains("alpha"));
311 assert!(rendered.contains("x86_64-pc-windows-msvc"));
312 assert!(rendered.contains("cfg(windows)"));
313 }
314}