Skip to main content

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