dev_fuzz/producer.rs
1//! [`Producer`] integration: wrap a [`FuzzRun`] and emit a [`Report`]
2//! every time the producer runs.
3//!
4//! [`Producer`]: dev_report::Producer
5//! [`Report`]: dev_report::Report
6
7use dev_report::{CheckResult, Producer, Report, Severity};
8
9use crate::FuzzRun;
10
11/// `Producer` adapter that drives a [`FuzzRun`] and converts the result
12/// into a `Report`.
13///
14/// Subprocess failures (missing tool, missing nightly, target not
15/// found, libFuzzer harness error) map to a single failing
16/// `CheckResult` named `fuzz::<target>` with `Severity::Critical`. No
17/// panics.
18///
19/// # Example
20///
21/// ```no_run
22/// use dev_fuzz::{FuzzBudget, FuzzProducer, FuzzRun};
23/// use dev_report::Producer;
24/// use std::time::Duration;
25///
26/// let producer = FuzzProducer::new(
27/// FuzzRun::new("parse_input", "0.1.0")
28/// .budget(FuzzBudget::time(Duration::from_secs(60))),
29/// );
30/// let report = producer.produce();
31/// println!("{}", report.to_json().unwrap());
32/// ```
33pub struct FuzzProducer {
34 run: FuzzRun,
35}
36
37impl FuzzProducer {
38 /// Build a producer that drives the given [`FuzzRun`].
39 pub fn new(run: FuzzRun) -> Self {
40 Self { run }
41 }
42}
43
44impl Producer for FuzzProducer {
45 fn produce(&self) -> Report {
46 let target = self.run.target_name().to_string();
47 let version = self.run.subject_version().to_string();
48 let mut report = Report::new(&target, &version).with_producer("dev-fuzz");
49 match self.run.execute() {
50 Ok(result) => {
51 let r = result.into_report();
52 // `into_report` already populated `producer` and
53 // started/finished times; replace `report` with the
54 // result-derived one so we don't double-finish.
55 return r;
56 }
57 Err(e) => {
58 let detail = e.to_string();
59 let check = CheckResult::fail(format!("fuzz::{target}"), Severity::Critical)
60 .with_detail(detail)
61 .with_tag("fuzz")
62 .with_tag("subprocess");
63 report.push(check);
64 }
65 }
66 report.finish();
67 report
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn produce_returns_report_when_tool_missing() {
77 // Default runner image won't have cargo-fuzz + nightly installed
78 // alongside a fuzz target; the producer should surface that as
79 // a failing CheckResult rather than panicking.
80 let producer = FuzzProducer::new(FuzzRun::new("nonexistent_target", "0.0.0"));
81 let report = producer.produce();
82 assert_eq!(report.subject, "nonexistent_target");
83 assert!(!report.checks.is_empty());
84 }
85}