blobfs/
mock.rs

1// Copyright 2021 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
5//! Mock implementation of blobfs for blobfs::Client.
6
7use fuchsia_hash::Hash;
8use futures::StreamExt as _;
9use std::collections::HashSet;
10use std::convert::TryInto as _;
11use zx::HandleBased as _;
12use {fidl_fuchsia_fxfs as ffxfs, fidl_fuchsia_io as fio};
13
14/// A testing server implementation of /blob.
15///
16/// Mock does not handle requests until instructed to do so.
17pub struct Mock {
18    pub(super) stream: fio::DirectoryRequestStream,
19    pub(super) reader_stream: ffxfs::BlobReaderRequestStream,
20    pub(super) creator_stream: ffxfs::BlobCreatorRequestStream,
21}
22
23impl Mock {
24    /// Consume the next BlobCreator request, verifying it is intended to create the blob identified
25    /// by `merkle`. Fail the request with `e`.
26    ///
27    /// # Panics
28    ///
29    /// Panics on error or assertion violation (unexpected requests or a mismatched open call)
30    pub async fn fail_create(&mut self, merkle: Hash, e: ffxfs::CreateBlobError) {
31        match self.creator_stream.next().await {
32            Some(Ok(ffxfs::BlobCreatorRequest::Create { hash, allow_existing, responder })) => {
33                assert_eq!(Hash::from(hash), merkle);
34                assert!(!allow_existing);
35                let () = responder.send(Err(e)).unwrap();
36            }
37            other => panic!("unexpected request: {other:?}"),
38        }
39    }
40
41    /// Consume the next BlobCreator request, verifying it is intended to create the blob identified
42    /// by `merkle`. Return a `BlobWriter` for validating the writes.
43    ///
44    /// # Panics
45    ///
46    /// Panics on error or assertion violation (unexpected requests or a mismatched open call)
47    pub async fn expect_create_blob(&mut self, merkle: Hash) -> BlobWriter {
48        match self.creator_stream.next().await {
49            Some(Ok(ffxfs::BlobCreatorRequest::Create { hash, allow_existing, responder })) => {
50                assert_eq!(Hash::from(hash), merkle);
51                assert!(!allow_existing);
52                let (writer, stream) =
53                    fidl::endpoints::create_request_stream::<ffxfs::BlobWriterMarker>();
54                let () = responder.send(Ok(writer)).unwrap();
55                BlobWriter { stream, vmo: None }
56            }
57            other => panic!("unexpected request: {other:?}"),
58        }
59    }
60
61    /// Consume the next BlobReader request, verifying it is intended to open the blob identified
62    /// by `merkle`. Either serve the contents of `res.ok()` or fail the open with `res.err()`.
63    ///
64    /// # Panics
65    ///
66    /// Panics on error or assertion violation (unexpected requests or a mismatched open call)
67    pub async fn expect_open_blob(&mut self, merkle: Hash, res: Result<Vec<u8>, zx::Status>) {
68        match self.reader_stream.next().await {
69            Some(Ok(ffxfs::BlobReaderRequest::GetVmo { blob_hash, responder })) => {
70                assert_eq!(Hash::from(blob_hash), merkle);
71                match res {
72                    Ok(content) => {
73                        let vmo = zx::Vmo::create(content.len().try_into().unwrap()).unwrap();
74                        let () = vmo.write(&content, 0).unwrap();
75                        let () = responder.send(Ok(vmo)).unwrap();
76                    }
77                    Err(s) => {
78                        let () = responder.send(Err(s.into_raw())).unwrap();
79                    }
80                }
81            }
82            other => panic!("unexpected request: {other:?}"),
83        }
84    }
85
86    /// Consume N directory requests, verifying they are intended to determine whether the blobs
87    /// specified `readable` and `missing` are readable or not, responding to the check based on
88    /// which collection the hash is in.
89    ///
90    /// # Panics
91    ///
92    /// Panics on error or assertion violation (unexpected requests, request for unspecified blob)
93    pub async fn expect_readable_missing_checks(&mut self, readable: &[Hash], missing: &[Hash]) {
94        let mut readable = readable.iter().copied().collect::<HashSet<_>>();
95        let mut missing = missing.iter().copied().collect::<HashSet<_>>();
96
97        while !(readable.is_empty() && missing.is_empty()) {
98            match self.reader_stream.next().await {
99                Some(Ok(ffxfs::BlobReaderRequest::GetVmo { blob_hash, responder })) => {
100                    let hash = Hash::from(blob_hash);
101                    if readable.remove(&hash) {
102                        let vmo = zx::Vmo::create(0).unwrap();
103                        let () = responder.send(Ok(vmo)).unwrap();
104                    } else if missing.remove(&hash) {
105                        let () = responder.send(Err(zx::Status::NOT_FOUND.into_raw())).unwrap();
106                    } else {
107                        panic!("Unexpected blob existance check for {hash}");
108                    }
109                }
110                other => panic!("unexpected request: {other:?}"),
111            }
112        }
113    }
114
115    /// Expects and handles a call to [`Client::filter_to_missing_blobs`].
116    /// Verifies the call intends to determine whether the blobs specified in `readable` and
117    /// `missing` are readable or not, responding to the check based on which collection the hash is
118    /// in.
119    ///
120    /// # Panics
121    ///
122    /// Panics on error or assertion violation (unexpected requests, request for unspecified blob)
123    pub async fn expect_filter_to_missing_blobs_with_readable_missing_ids(
124        &mut self,
125        readable: &[Hash],
126        missing: &[Hash],
127    ) {
128        self.expect_readable_missing_checks(readable, missing).await;
129    }
130
131    /// Asserts that the request stream closes without any further requests.
132    ///
133    /// # Panics
134    ///
135    /// Panics on error
136    pub async fn expect_done(mut self) {
137        match self.stream.next().await {
138            None => {}
139            Some(request) => panic!("unexpected request: {request:?}"),
140        }
141    }
142}
143
144/// A testing server implementation of fuchsia.fxfs/BlobWriter.
145pub struct BlobWriter {
146    stream: ffxfs::BlobWriterRequestStream,
147    vmo: Option<zx::Vmo>,
148}
149
150impl BlobWriter {
151    /// Asserts that the request stream closes without any further requests.
152    ///
153    /// # Panics
154    ///
155    /// Panics on error
156    pub async fn expect_done(mut self) {
157        match self.stream.next().await {
158            None => {}
159            Some(request) => panic!("unexpected request: {request:?}"),
160        }
161    }
162
163    /// Asserts that GetVmo is called with the indicated size.
164    ///
165    /// # Panics
166    ///
167    /// Panics on error
168    pub async fn expect_get_vmo(&mut self, expected_size: u64) -> &mut Self {
169        match self.stream.next().await {
170            Some(Ok(ffxfs::BlobWriterRequest::GetVmo { size, responder })) => {
171                assert_eq!(expected_size, size);
172                let vmo = zx::Vmo::create(expected_size).unwrap();
173                assert!(self.vmo.is_none());
174                self.vmo = Some(vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap());
175                let () = responder.send(Ok(vmo)).unwrap();
176            }
177            req => panic!("unexpected request {req:?}"),
178        }
179        self
180    }
181
182    /// Asserts that BytesWritten is called and responds with a data integrity error.
183    ///
184    /// # Panics
185    ///
186    /// Panics on error
187    pub async fn fail_bytes_written(&mut self) -> &mut Self {
188        match self.stream.next().await {
189            Some(Ok(ffxfs::BlobWriterRequest::BytesReady { bytes_written: _, responder })) => {
190                let () = responder.send(Err(zx::Status::IO_DATA_INTEGRITY.into_raw())).unwrap();
191            }
192            req => panic!("unexpected request {req:?}"),
193        }
194        self
195    }
196
197    /// Asserts that `content` is written to this freshly created `BlobWriter` in a single
198    /// `BytesWritten`.
199    ///
200    /// # Panics
201    ///
202    /// Panics on error
203    pub async fn expect_payload(mut self, content: &[u8]) {
204        self.expect_get_vmo(content.len().try_into().unwrap()).await;
205        match self.stream.next().await {
206            Some(Ok(ffxfs::BlobWriterRequest::BytesReady { bytes_written, responder })) => {
207                assert_eq!(bytes_written, u64::try_from(content.len()).unwrap());
208                let vmo = self.vmo.unwrap();
209                let mut buf = vec![0; content.len()];
210                let () = vmo.read(&mut buf, 0).unwrap();
211                assert_eq!(content, &buf[..content.len()]);
212                let () = responder.send(Ok(())).unwrap();
213            }
214            req => panic!("unexpected request {req:?}"),
215        }
216    }
217}