Skip to main content

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}