fuchsia_fuzzctl/
controller.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::constants::*;
6use crate::corpus;
7use crate::diagnostics::Forwarder;
8use crate::duration::deadline_after;
9use crate::input::{Input, InputPair};
10use crate::writer::{OutputSink, Writer};
11use anyhow::{anyhow, bail, Context as _, Error, Result};
12use fidl::endpoints::create_request_stream;
13use fidl::Error::ClientChannelClosed;
14use fidl_fuchsia_fuzzer::{self as fuzz, Artifact as FidlArtifact};
15use fuchsia_async::Timer;
16use futures::future::{pending, Either};
17use futures::{pin_mut, select, try_join, Future, FutureExt};
18use std::cell::RefCell;
19use std::cmp::max;
20use std::path::Path;
21use zx_status as zx;
22
23/// Represents a `fuchsia.fuzzer.Controller` connection to a fuzzer.
24#[derive(Debug)]
25pub struct Controller<O: OutputSink> {
26    proxy: fuzz::ControllerProxy,
27    forwarder: Forwarder<O>,
28    min_timeout: i64,
29    timeout: RefCell<Option<i64>>,
30}
31
32impl<O: OutputSink> Controller<O> {
33    /// Returns a new Controller instance.
34    pub fn new(proxy: fuzz::ControllerProxy, writer: &Writer<O>) -> Self {
35        Self {
36            proxy,
37            forwarder: Forwarder::<O>::new(writer),
38            min_timeout: 60 * NANOS_PER_SECOND,
39            timeout: RefCell::new(None),
40        }
41    }
42
43    /// Registers the provided output socket with the forwarder.
44    pub fn set_output<P: AsRef<Path>>(
45        &mut self,
46        socket: fidl::Socket,
47        output: fuzz::TestOutput,
48        logs_dir: &Option<P>,
49    ) -> Result<()> {
50        self.forwarder.set_output(socket, output, logs_dir)
51    }
52
53    /// Sets the minimum amount of time, in nanoseconds, before a workflow can time out.
54    ///
55    /// If a the `max_total_time` option is set for a workflow that hangs, it will eventually
56    /// timeout. This method can be used to specify the minimum duration that must elapse before a
57    /// workflow is considered hung. The default of 1 minute is usually appropriate, but this method
58    /// can be useful when testing.
59    ///
60    pub fn set_min_timeout(&mut self, min_timeout: i64) {
61        self.min_timeout = min_timeout;
62    }
63
64    /// Sets various execution and error detection parameters for the fuzzer.
65    ///
66    /// Returns an error if:
67    ///   * Communicating with the fuzzer fails
68    ///   * A long-running call such as `try_one`, `fuzz`, `cleanse`, `minimize`, or `merge` is in
69    ///     progress.
70    ///
71    pub async fn configure(&self, options: fuzz::Options) -> Result<()> {
72        self.set_timeout(&options, None);
73        let result =
74            self.proxy.configure(&options).await.context("fuchsia.fuzzer/Controller.Configure")?;
75        result.map_err(|status| {
76            anyhow!("fuchsia.fuzzer/Controller.Configure returned: ZX_ERR_{}", status)
77        })
78    }
79
80    /// Returns a fuzzer's current values for the various execution and error detection parameters.
81    ///
82    /// Returns an error if communicating with the fuzzer fails
83    ///
84    pub async fn get_options(&self) -> Result<fuzz::Options> {
85        self.proxy
86            .get_options()
87            .await
88            .map_err(Error::msg)
89            .context("`fuchsia.fuzzer.Controller/GetOptions` failed")
90    }
91
92    /// Recalculates the timeout for a long-running workflow based on the configured maximum total
93    /// time and reported elapsed time.
94    ///
95    /// Returns an error if communicating with the fuzzer fails
96    ///
97    pub async fn reset_timer(&self) -> Result<()> {
98        let options = self.get_options().await?;
99        let status = self.get_status().await?;
100        self.set_timeout(&options, Some(status));
101        Ok(())
102    }
103
104    // Sets a workflow timeout based on the maximum total time a fuzzer workflow is expected to run.
105    fn set_timeout(&self, options: &fuzz::Options, status: Option<fuzz::Status>) {
106        let elapsed = status.map(|s| s.elapsed.unwrap_or(0)).unwrap_or(0);
107        if let Some(max_total_time) = options.max_total_time {
108            let mut timeout_mut = self.timeout.borrow_mut();
109            match max_total_time {
110                0 => {
111                    *timeout_mut = None;
112                }
113                n => {
114                    *timeout_mut = Some(max(n * 2, self.min_timeout) - elapsed);
115                }
116            }
117        }
118    }
119
120    /// Retrieves test inputs from one of the fuzzer's corpora.
121    ///
122    /// The compacted corpus is saved to the `corpus_dir`. Returns details on how much data was
123    /// received.
124    ///
125    /// Returns an error if:
126    ///   * Communicating with the fuzzer fails.
127    ///   * One or more inputs fails to be received and saved.
128    ///
129    pub async fn read_corpus<P: AsRef<Path>>(
130        &self,
131        corpus_type: fuzz::Corpus,
132        corpus_dir: P,
133    ) -> Result<corpus::Stats> {
134        let (client_end, stream) = create_request_stream::<fuzz::CorpusReaderMarker>();
135        let (_, corpus_stats) = try_join!(
136            async { self.proxy.read_corpus(corpus_type, client_end).await.map_err(Error::msg) },
137            async { corpus::read(stream, corpus_dir).await },
138        )
139        .context("`fuchsia.fuzzer.Controller/ReadCorpus` failed")?;
140        Ok(corpus_stats)
141    }
142
143    /// Adds a test input to one of the fuzzer's corpora.
144    ///
145    /// The `test_input` may be either a single file or a directory. Returns details on how much
146    /// data was sent.
147    ///
148    /// Returns an error if:
149    ///   * Converting the input to an `Input`/`fuchsia.fuzzer.Input` pair fails.
150    ///   * Communicating with the fuzzer fails
151    ///   * The fuzzer returns an error, e.g. if it failed to transfer the input.
152    ///
153    pub async fn add_to_corpus(
154        &self,
155        input_pairs: Vec<InputPair>,
156        corpus_type: fuzz::Corpus,
157    ) -> Result<corpus::Stats> {
158        let expected_num_inputs = input_pairs.len();
159        let expected_total_size =
160            input_pairs.iter().fold(0, |total, input_pair| total + input_pair.len());
161        let mut corpus_stats = corpus::Stats { num_inputs: 0, total_size: 0 };
162        for input_pair in input_pairs.into_iter() {
163            let (fidl_input, input) = input_pair.as_tuple();
164            let fidl_input_size = fidl_input.size;
165            let (result, _) = try_join!(
166                async {
167                    self.proxy.add_to_corpus(corpus_type, fidl_input).await.map_err(Error::msg)
168                },
169                input.send(),
170            )
171            .context("fuchsia.fuzzer/Controller.AddToCorpus failed")?;
172            if let Err(status) = result {
173                bail!(
174                    "fuchsia.fuzzer/Controller.AddToCorpus returned: ZX_ERR_{} \
175                       after writing {} of {} files ({} of {} bytes)",
176                    status,
177                    corpus_stats.num_inputs,
178                    expected_num_inputs,
179                    corpus_stats.total_size,
180                    expected_total_size
181                )
182            }
183            corpus_stats.num_inputs += 1;
184            corpus_stats.total_size += fidl_input_size;
185        }
186        Ok(corpus_stats)
187    }
188
189    /// Returns information about fuzzer execution.
190    ///
191    /// The status typically includes information such as how long the fuzzer has been running, how
192    /// many edges in the call graph have been covered, how large the corpus is, etc.
193    ///
194    /// Refer to `fuchsia.fuzzer.Status` for precise details on the returned information.
195    ///
196    pub async fn get_status(&self) -> Result<fuzz::Status> {
197        match self.proxy.get_status().await {
198            Err(fidl::Error::ClientChannelClosed { status, .. })
199                if status == zx::Status::PEER_CLOSED =>
200            {
201                return Ok(fuzz::Status::default())
202            }
203            Err(e) => bail!("`fuchsia.fuzzer.Controller/GetStatus` failed: {:?}", e),
204            Ok(fuzz_status) => Ok(fuzz_status),
205        }
206    }
207
208    /// Runs the fuzzer in a loop to generate and test new inputs.
209    ///
210    /// The fuzzer will continuously generate new inputs and tries them until one of four
211    /// conditions are met:
212    ///   * The number of inputs tested exceeds the configured number of `runs`.
213    ///   * The configured amount of `max_total_time` has elapsed.
214    ///   * An input triggers a fatal error, e.g. death by AddressSanitizer.
215    ///   * `fuchsia.fuzzer.Controller/Stop` is called.
216    ///
217    /// Returns an error if:
218    ///   * Either `runs` or `time` is provided but cannot be parsed to  a valid value.
219    ///   * Communicating with the fuzzer fails.
220    ///   * The fuzzer returns an error, e.g. it is already performing another workflow.
221    ///
222    pub async fn fuzz(&self) -> Result<()> {
223        let response = self.proxy.fuzz().await;
224        let status = check_response("Fuzz", response)?;
225        check_status("Fuzz", status)
226    }
227
228    /// Tries running the fuzzer once using the given input.
229    ///
230    /// Returns an error if:
231    ///   * Converting the input to an `Input`/`fuchsia.fuzzer.Input` pair fails.
232    ///   * Communicating with the fuzzer fails.
233    ///   * The fuzzer returns an error, e.g. it is already performing another workflow.
234    ///
235    pub async fn try_one(&self, input_pair: InputPair) -> Result<()> {
236        let (fidl_input, input) = input_pair.as_tuple();
237        let status = self.with_input("TryOne", self.proxy.try_one(fidl_input), input).await?;
238        check_status("TryOne", status)
239    }
240
241    /// Reduces the length of an error-causing input while preserving the error.
242    ///
243    /// The fuzzer will bound its attempt to find shorter inputs using the given `runs` or `time`,
244    /// if provided.
245    ///
246    /// Returns an error if:
247    ///   * Either `runs` or `time` is provided but cannot be parsed to  a valid value.
248    ///   * Converting the input to an `Input`/`fuchsia.fuzzer.Input` pair fails.
249    ///   * Communicating with the fuzzer fails.
250    ///   * The fuzzer returns an error, e.g. it is already performing another workflow.
251    ///   * The minimized input fails to be received and saved.
252    ///
253    pub async fn minimize(&self, input_pair: InputPair) -> Result<()> {
254        let (fidl_input, input) = input_pair.as_tuple();
255        let status = self.with_input("Minimize", self.proxy.minimize(fidl_input), input).await?;
256        match status {
257            zx::Status::INVALID_ARGS => bail!("the provided input did not cause an error"),
258            status => check_status("Minimize", status),
259        }
260    }
261
262    /// Replaces bytes in a error-causing input with PII-safe bytes, e.g. spaces.
263    ///
264    /// The fuzzer will try to reproduce the error caused by the input with each byte replaced by a
265    /// fixed number of "clean" candidates.
266    ///
267    /// Returns an error if:
268    ///   * Converting the input to an `Input`/`fuchsia.fuzzer.Input` pair fails.
269    ///   * Communicating with the fuzzer fails.
270    ///   * The fuzzer returns an error, e.g. it is already performing another workflow.
271    ///   * The cleansed input fails to be received and saved.
272    ///
273    pub async fn cleanse(&self, input_pair: InputPair) -> Result<()> {
274        let (fidl_input, input) = input_pair.as_tuple();
275        let status = self.with_input("Cleanse", self.proxy.cleanse(fidl_input), input).await?;
276        match status {
277            zx::Status::INVALID_ARGS => bail!("the provided input did not cause an error"),
278            status => check_status("Cleanse", status),
279        }
280    }
281
282    /// Removes inputs from the corpus that produce duplicate coverage.
283    ///
284    /// The fuzzer makes a finite number of passes over its seed and live corpora. The seed corpus
285    /// is unchanged, but the fuzzer will try to find the set of shortest inputs that preserves
286    /// coverage.
287    ///
288    /// Returns an error if:
289    ///   * Communicating with the fuzzer fails.
290    ///   * The fuzzer returns an error, e.g. it is already performing another workflow.
291    ///   * One or more inputs fails to be received and saved.
292    ///
293    pub async fn merge(&self) -> Result<()> {
294        let response = self.proxy.merge().await;
295        let status = check_response("Merge", response)?;
296        match status {
297            zx::Status::INVALID_ARGS => bail!("an input in the seed corpus triggered an error"),
298            status => check_status("Merge", status),
299        }
300    }
301
302    // Runs the given `fidl_fut` along with a future to send an `input`.
303    async fn with_input<F>(&self, name: &str, fidl_fut: F, input: Input) -> Result<zx::Status>
304    where
305        F: Future<Output = Result<Result<(), i32>, fidl::Error>>,
306    {
307        let fidl_fut = fidl_fut.fuse();
308        let send_fut = input.send().fuse();
309        let timer_fut = match deadline_after(*self.timeout.borrow()) {
310            Some(deadline) => Either::Left(Timer::new(deadline)),
311            None => Either::Right(pending()),
312        };
313        let timer_fut = timer_fut.fuse();
314        pin_mut!(fidl_fut, send_fut, timer_fut);
315        let mut remaining = 2;
316        let mut status = zx::Status::OK;
317        // If `fidl_fut` completes with e.g. `Ok(zx::Status::CANCELED)`, drop
318        // the `send_fut` and `forward_fut` futures.
319        while remaining > 0 && status == zx::Status::OK {
320            select! {
321                response = fidl_fut => {
322                    status = check_response(name, response)?;
323                    remaining -= 1;
324                }
325                result = send_fut => {
326                    result?;
327                    remaining -= 1;
328                }
329                _ = timer_fut => {
330                    bail!("workflow timed out");
331                }
332            };
333        }
334        Ok(status)
335    }
336    /// Waits for the results of a long-running workflow.
337    ///
338    /// The `fuchsia.fuzzer.Controller/WatchArtifact` method uses a
339    /// ["hanging get" pattern](https://fuchsia.dev/fuchsia-src/development/api/fidl#hanging-get).
340    /// The first call will return whatever the current artifact is for the fuzzer; subsequent calls
341    /// will block until the artifact changes. The implementation below may retry the FIDL method to
342    /// ensure it only returns `Ok(None)` on channel close.
343    ///
344    pub async fn watch_artifact(&self) -> Result<FidlArtifact> {
345        let watch_fut = || async move {
346            loop {
347                let artifact = self.proxy.watch_artifact().await?;
348                if artifact != FidlArtifact::default() {
349                    return Ok(artifact);
350                }
351            }
352        };
353        let watch_fut = watch_fut().fuse();
354        let forward_fut = self.forwarder.forward_all().fuse();
355        let timer_fut = match deadline_after(*self.timeout.borrow()) {
356            Some(deadline) => Either::Left(Timer::new(deadline)),
357            None => Either::Right(pending()),
358        };
359        let timer_fut = timer_fut.fuse();
360        pin_mut!(watch_fut, forward_fut, timer_fut);
361        let mut remaining = 2;
362        let mut fidl_artifact = FidlArtifact::default();
363        // If `fidl_fut` completes with e.g. `Ok(zx::Status::CANCELED)`, drop
364        // the `send_fut` and `forward_fut` futures.
365        while remaining > 0 {
366            select! {
367                result = watch_fut => {
368                    fidl_artifact = match result {
369                        Ok(fidl_artifact) => {
370                            if let Some(e) = fidl_artifact.error {
371                                bail!("workflow returned an error: ZX_ERR_{}", e);
372                            }
373                            fidl_artifact
374                        }
375                        Err(ClientChannelClosed { status, .. }) if status == zx::Status::PEER_CLOSED => FidlArtifact { error: Some(zx::Status::CANCELED.into_raw()), ..Default::default() },
376                        Err(e) => bail!("fuchsia.fuzzer/Controller.WatchArtifact: {:?}", e),
377                    };
378                    remaining -= 1;
379                }
380                result = forward_fut => {
381                    result?;
382                    remaining -= 1;
383                }
384                _ = timer_fut => {
385                    bail!("workflow timed out");
386                }
387            };
388        }
389        Ok(fidl_artifact)
390    }
391}
392
393// Checks a FIDL response for generic errors.
394fn check_response(
395    name: &str,
396    response: Result<Result<(), i32>, fidl::Error>,
397) -> Result<zx::Status> {
398    match response {
399        Err(fidl::Error::ClientChannelClosed { status, .. })
400            if status == zx::Status::PEER_CLOSED =>
401        {
402            Ok(zx::Status::OK)
403        }
404        Err(e) => bail!("`fuchsia.fuzzer.Controller/{}` failed: {:?}", name, e),
405        Ok(Err(raw)) => Ok(zx::Status::from_raw(raw)),
406        Ok(Ok(())) => Ok(zx::Status::OK),
407    }
408}
409
410// Checks the result from a FIDL response for common errors.
411fn check_status(name: &str, status: zx::Status) -> Result<()> {
412    match status {
413        zx::Status::OK => Ok(()),
414        zx::Status::BAD_STATE => bail!("another long-running workflow is in progress"),
415        status => bail!("`fuchsia.fuzzer.Controller/{}` returned: ZX_ERR_{}", name, status),
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use crate::util::digest_path;
422    use anyhow::{Context as _, Result};
423    use fidl::endpoints::create_proxy_and_stream;
424    use fidl_fuchsia_fuzzer::{self as fuzz, Result_ as FuzzResult};
425    use fuchsia_fuzzctl::{Controller, Input, InputPair};
426    use fuchsia_fuzzctl_test::{create_task, serve_controller, verify_saved, FakeController, Test};
427    use {fuchsia_async as fasync, zx_status as zx};
428
429    // Creates a test setup suitable for unit testing `Controller`.
430    fn perform_test_setup(
431        test: &Test,
432    ) -> Result<(FakeController, fuzz::ControllerProxy, fasync::Task<()>)> {
433        let fake = test.controller();
434        let (proxy, stream) = create_proxy_and_stream::<fuzz::ControllerMarker>();
435        let task = create_task(serve_controller(stream, test.clone()), test.writer());
436        Ok((fake, proxy, task))
437    }
438
439    #[fuchsia::test]
440    async fn test_configure() -> Result<()> {
441        let test = Test::try_new()?;
442        let (fake, proxy, _task) = perform_test_setup(&test)?;
443        let controller = Controller::new(proxy, test.writer());
444
445        // Modify all the options that start with 'd'.
446        let expected = fuzz::Options {
447            dictionary_level: Some(1),
448            detect_exits: Some(true),
449            detect_leaks: Some(false),
450            death_exitcode: Some(2),
451            debug: Some(true),
452            ..Default::default()
453        };
454        controller.configure(expected.clone()).await?;
455        let actual = fake.get_options();
456        assert_eq!(actual.dictionary_level, expected.dictionary_level);
457        assert_eq!(actual.detect_exits, expected.detect_exits);
458        assert_eq!(actual.detect_leaks, expected.detect_leaks);
459        assert_eq!(actual.death_exitcode, expected.death_exitcode);
460        assert_eq!(actual.debug, expected.debug);
461
462        Ok(())
463    }
464
465    #[fuchsia::test]
466    async fn test_get_options() -> Result<()> {
467        let test = Test::try_new()?;
468        let (fake, proxy, _task) = perform_test_setup(&test)?;
469        let controller = Controller::new(proxy, test.writer());
470
471        // Modify all the options that start with 'm'.
472        let expected = fuzz::Options {
473            max_total_time: Some(20000),
474            max_input_size: Some(2000),
475            mutation_depth: Some(20),
476            malloc_limit: Some(200),
477            malloc_exitcode: Some(2),
478            ..Default::default()
479        };
480        fake.set_options(expected.clone());
481        let actual = controller.get_options().await?;
482        assert_eq!(actual.max_total_time, expected.max_total_time);
483        assert_eq!(actual.max_input_size, expected.max_input_size);
484        assert_eq!(actual.mutation_depth, expected.mutation_depth);
485        assert_eq!(actual.malloc_limit, expected.malloc_limit);
486        assert_eq!(actual.malloc_exitcode, expected.malloc_exitcode);
487
488        Ok(())
489    }
490
491    #[fuchsia::test]
492    async fn test_read_corpus() -> Result<()> {
493        let test = Test::try_new()?;
494        let (fake, proxy, _task) = perform_test_setup(&test)?;
495        let controller = Controller::new(proxy, test.writer());
496
497        let seed_dir = test.create_dir("seed")?;
498        fake.set_input_to_send(b"foo");
499        let stats = controller.read_corpus(fuzz::Corpus::Seed, &seed_dir).await?;
500        assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Seed);
501        assert_eq!(stats.num_inputs, 1);
502        assert_eq!(stats.total_size, 3);
503        let path = digest_path(&seed_dir, None, b"foo");
504        verify_saved(&path, b"foo")?;
505
506        let live_dir = test.create_dir("live")?;
507        fake.set_input_to_send(b"barbaz");
508        let stats = controller.read_corpus(fuzz::Corpus::Live, &live_dir).await?;
509        assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Live);
510        assert_eq!(stats.num_inputs, 1);
511        assert_eq!(stats.total_size, 6);
512        let path = digest_path(&live_dir, None, b"barbaz");
513        verify_saved(&path, b"barbaz")?;
514
515        Ok(())
516    }
517
518    #[fuchsia::test]
519    async fn test_add_to_corpus() -> Result<()> {
520        let test = Test::try_new()?;
521        let (fake, proxy, _task) = perform_test_setup(&test)?;
522        let controller = Controller::new(proxy, test.writer());
523
524        let input_pairs: Vec<InputPair> = vec![b"foo".to_vec(), b"bar".to_vec(), b"baz".to_vec()]
525            .into_iter()
526            .map(|data| InputPair::try_from_data(data).unwrap())
527            .collect();
528        let stats = controller.add_to_corpus(input_pairs, fuzz::Corpus::Seed).await?;
529        assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Seed);
530        assert_eq!(stats.num_inputs, 3);
531        assert_eq!(stats.total_size, 9);
532
533        let input_pairs: Vec<InputPair> =
534            vec![b"qux".to_vec(), b"quux".to_vec(), b"corge".to_vec()]
535                .into_iter()
536                .map(|data| InputPair::try_from_data(data).unwrap())
537                .collect();
538        let stats = controller.add_to_corpus(input_pairs, fuzz::Corpus::Live).await?;
539        assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Live);
540        assert_eq!(stats.num_inputs, 3);
541        assert_eq!(stats.total_size, 12);
542
543        Ok(())
544    }
545
546    #[fuchsia::test]
547    async fn test_get_status() -> Result<()> {
548        let test = Test::try_new()?;
549        let (fake, proxy, _task) = perform_test_setup(&test)?;
550        let controller = Controller::new(proxy, test.writer());
551
552        let expected = fuzz::Status {
553            running: Some(true),
554            runs: Some(1),
555            elapsed: Some(2),
556            covered_pcs: Some(3),
557            covered_features: Some(4),
558            corpus_num_inputs: Some(5),
559            corpus_total_size: Some(6),
560            process_stats: None,
561            ..Default::default()
562        };
563        fake.set_status(expected.clone());
564        let actual = controller.get_status().await?;
565        assert_eq!(actual, expected);
566
567        Ok(())
568    }
569
570    #[fuchsia::test]
571    async fn test_try_one() -> Result<()> {
572        let test = Test::try_new()?;
573        let (fake, proxy, _task) = perform_test_setup(&test)?;
574        let controller = Controller::new(proxy, test.writer());
575
576        let input_pair = InputPair::try_from_data(b"foo".to_vec())?;
577        controller.try_one(input_pair).await?;
578        let artifact = controller.watch_artifact().await?;
579        assert_eq!(artifact.error, None);
580        assert_eq!(artifact.result, Some(FuzzResult::NoErrors));
581
582        fake.set_result(Ok(FuzzResult::Crash));
583        let input_pair = InputPair::try_from_data(b"bar".to_vec())?;
584        controller.try_one(input_pair).await?;
585        let artifact = controller.watch_artifact().await?;
586        assert_eq!(artifact.error, None);
587        assert_eq!(artifact.result, Some(FuzzResult::Crash));
588
589        fake.cancel();
590        let input_pair = InputPair::try_from_data(b"baz".to_vec())?;
591        controller.try_one(input_pair).await?;
592        let artifact = controller.watch_artifact().await?;
593        assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
594
595        Ok(())
596    }
597
598    #[fuchsia::test]
599    async fn test_fuzz() -> Result<()> {
600        let test = Test::try_new()?;
601        let (fake, proxy, _task) = perform_test_setup(&test)?;
602        let controller = Controller::new(proxy, test.writer());
603
604        let options = fuzz::Options { runs: Some(10), ..Default::default() };
605        controller.configure(options).await?;
606        controller.fuzz().await?;
607        let artifact = controller.watch_artifact().await?;
608        assert_eq!(artifact.error, None);
609        assert_eq!(artifact.result, Some(FuzzResult::NoErrors));
610
611        fake.set_result(Ok(FuzzResult::Death));
612        fake.set_input_to_send(b"foo");
613        controller.fuzz().await?;
614        let artifact = controller.watch_artifact().await?;
615        assert_eq!(artifact.error, None);
616        assert_eq!(artifact.result, Some(FuzzResult::Death));
617
618        let fidl_input = artifact.input.context("invalid FIDL artifact")?;
619        let input = Input::try_receive(fidl_input).await?;
620        assert_eq!(input.data, b"foo");
621
622        fake.cancel();
623        controller.fuzz().await?;
624        let artifact = controller.watch_artifact().await?;
625        assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
626
627        Ok(())
628    }
629
630    #[fuchsia::test]
631    async fn test_minimize() -> Result<()> {
632        let test = Test::try_new()?;
633        let (fake, proxy, _task) = perform_test_setup(&test)?;
634        let controller = Controller::new(proxy, test.writer());
635
636        fake.set_input_to_send(b"foo");
637        let input_pair = InputPair::try_from_data(b"foofoofoo".to_vec())?;
638        controller.minimize(input_pair).await?;
639        let artifact = controller.watch_artifact().await?;
640        assert_eq!(artifact.error, None);
641        assert_eq!(artifact.result, Some(FuzzResult::Minimized));
642
643        let fidl_input = artifact.input.context("invalid FIDL artifact")?;
644        let input = Input::try_receive(fidl_input).await?;
645        assert_eq!(input.data, b"foo");
646
647        fake.cancel();
648        let input_pair = InputPair::try_from_data(b"bar".to_vec())?;
649        controller.minimize(input_pair).await?;
650        let artifact = controller.watch_artifact().await?;
651        assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
652
653        Ok(())
654    }
655
656    #[fuchsia::test]
657    async fn test_cleanse() -> Result<()> {
658        let test = Test::try_new()?;
659        let (fake, proxy, _task) = perform_test_setup(&test)?;
660        let controller = Controller::new(proxy, test.writer());
661
662        fake.set_input_to_send(b"   bar   ");
663        let input_pair = InputPair::try_from_data(b"foobarbaz".to_vec())?;
664        controller.cleanse(input_pair).await?;
665        let artifact = controller.watch_artifact().await?;
666        assert_eq!(artifact.error, None);
667        assert_eq!(artifact.result, Some(FuzzResult::Cleansed));
668
669        let fidl_input = artifact.input.context("invalid FIDL artifact")?;
670        let input = Input::try_receive(fidl_input).await?;
671        assert_eq!(input.data, b"   bar   ");
672
673        fake.cancel();
674        let input_pair = InputPair::try_from_data(b"foobarbaz".to_vec())?;
675        controller.cleanse(input_pair).await?;
676        let artifact = controller.watch_artifact().await?;
677        assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
678
679        Ok(())
680    }
681
682    #[fuchsia::test]
683    async fn test_merge() -> Result<()> {
684        let test = Test::try_new()?;
685        let (fake, proxy, _task) = perform_test_setup(&test)?;
686        let controller = Controller::new(proxy, test.writer());
687
688        controller.merge().await?;
689        let artifact = controller.watch_artifact().await?;
690        assert_eq!(artifact.error, None);
691        assert_eq!(artifact.result, Some(FuzzResult::Merged));
692
693        fake.cancel();
694        controller.merge().await?;
695        let artifact = controller.watch_artifact().await?;
696        assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
697
698        Ok(())
699    }
700}