1#![allow(clippy::let_unit_value)]
6
7use anyhow::{Context, Error};
8use fidl::endpoints::{ClientEnd, DiscoverableProtocolMarker, Proxy, ServerEnd};
9use fidl_fuchsia_io as fio;
10use fidl_fuchsia_paver::PaverRequestStream;
11use fidl_fuchsia_pkg_ext::RepositoryConfigs;
12use fuchsia_async as fasync;
13use fuchsia_component::server::ServiceFs;
14use fuchsia_component_test::LocalComponentHandles;
15use fuchsia_merkle::Hash;
16use fuchsia_pkg_testing::serve::ServedRepository;
17use fuchsia_pkg_testing::{Package, RepositoryBuilder};
18use futures::prelude::*;
19use isolated_ota::{OmahaConfig, UpdateUrlSource};
20use mock_omaha_server::{
21 OmahaResponse, OmahaServer, OmahaServerBuilder, ResponseAndMetadata, ResponseMap,
22};
23use mock_omaha_server_fuchsia::OmahaServerExt as _;
24use mock_paver::{MockPaverService, MockPaverServiceBuilder};
25use std::collections::{BTreeMap, BTreeSet};
26use std::io::Write;
27use std::str::FromStr;
28use std::sync::{Arc, Mutex};
29use tempfile::TempDir;
30
31const EMPTY_REPO_PATH: &str = "/pkg/empty-repo";
32const TEST_REPO_URL: &str = "fuchsia-pkg://integration.test.fuchsia.com";
33
34pub enum OmahaState {
35 Disabled(Option<http::Uri>),
37 Auto(OmahaResponse),
39 Manual(OmahaConfig),
41}
42
43pub struct TestParams {
44 pub blobfs: Option<ClientEnd<fio::DirectoryMarker>>,
45 pub board: String,
46 pub channel: String,
47 pub expected_blobfs_contents: BTreeSet<Hash>,
48 pub paver: Arc<MockPaverService>,
49 pub repo_config_dir: TempDir,
50 pub update_merkle: Hash,
51 pub version: String,
52 pub update_url_source: UpdateUrlSource,
53 pub paver_connector: ClientEnd<fio::DirectoryMarker>,
54 pub system_image_hash: Option<Hash>,
55}
56
57pub async fn expose_mock_paver(
63 handles: LocalComponentHandles,
64 paver_dir_proxy: fio::DirectoryProxy,
65) -> Result<(), Error> {
66 let mut fs = ServiceFs::new();
67
68 fs.dir("svc").add_service_connector(
69 move |server_end: ServerEnd<fidl_fuchsia_paver::PaverMarker>| {
70 fdio::service_connect_at(
71 paver_dir_proxy.as_channel().as_ref(),
72 &format!("/{}", fidl_fuchsia_paver::PaverMarker::PROTOCOL_NAME),
73 server_end.into_channel(),
74 )
75 .expect("failed to connect to paver service node");
76 },
77 );
78
79 fs.serve_connection(handles.outgoing_dir).expect("failed to serve paver fs connection");
80 fs.collect::<()>().await;
81 Ok(())
82}
83
84#[allow(async_fn_in_trait)]
85pub trait TestExecutor<R> {
86 async fn run(&self, params: TestParams) -> R;
87}
88
89pub struct TestEnvBuilder<E> {
90 blobfs: Option<ClientEnd<fio::DirectoryMarker>>,
91 board: String,
92 channel: String,
93 omaha: OmahaState,
94 packages: Vec<Package>,
95 paver: MockPaverServiceBuilder,
96 repo_config: Option<RepositoryConfigs>,
97 version: String,
98 test_executor: Option<E>,
99 fuchsia_image: Option<(Vec<u8>, Option<Vec<u8>>)>,
101 recovery_image: Option<(Vec<u8>, Option<Vec<u8>>)>,
103 firmware_images: BTreeMap<String, Vec<u8>>,
104 system_image_hash: Option<Hash>,
105}
106
107impl<E> TestEnvBuilder<E> {
108 #[allow(clippy::new_without_default)]
109 pub fn new() -> Self {
110 TestEnvBuilder {
111 blobfs: None,
112 board: "test-board".to_owned(),
113 channel: "test".to_owned(),
114 omaha: OmahaState::Disabled(Some(format!("{TEST_REPO_URL}/update").parse().unwrap())),
115 packages: vec![],
116 paver: MockPaverServiceBuilder::new(),
117 repo_config: None,
118 version: "0.1.2.3".to_owned(),
119 test_executor: None,
120 fuchsia_image: None,
121 recovery_image: None,
122 firmware_images: BTreeMap::new(),
123 system_image_hash: None,
124 }
125 }
126
127 pub fn add_package(mut self, pkg: Package) -> Self {
131 self.packages.push(pkg);
132 self
133 }
134
135 pub fn blobfs(mut self, client: ClientEnd<fio::DirectoryMarker>) -> Self {
136 self.blobfs = Some(client);
137 self
138 }
139
140 pub fn repo_config(mut self, repo: RepositoryConfigs) -> Self {
143 self.repo_config = Some(repo);
144 self
145 }
146
147 pub fn system_image_hash(mut self, hash: Hash) -> Self {
148 self.system_image_hash = Some(hash);
149 self
150 }
151
152 pub fn omaha_state(mut self, state: OmahaState) -> Self {
155 self.omaha = state;
156 self
157 }
158
159 pub fn paver<F>(mut self, func: F) -> Self
161 where
162 F: FnOnce(MockPaverServiceBuilder) -> MockPaverServiceBuilder,
163 {
164 self.paver = func(self.paver);
165 self
166 }
167
168 pub fn test_executor(mut self, executor: E) -> Self {
169 self.test_executor = Some(executor);
170 self
171 }
172
173 pub fn fuchsia_image(mut self, zbi: Vec<u8>, vbmeta: Option<Vec<u8>>) -> Self {
175 assert_eq!(self.fuchsia_image, None);
176 self.fuchsia_image = Some((zbi, vbmeta));
177 self
178 }
179
180 pub fn recovery_image(mut self, zbi: Vec<u8>, vbmeta: Option<Vec<u8>>) -> Self {
182 assert_eq!(self.recovery_image, None);
183 self.recovery_image = Some((zbi, vbmeta));
184 self
185 }
186
187 pub fn firmware_image(mut self, type_: String, content: Vec<u8>) -> Self {
189 assert_eq!(self.firmware_images.insert(type_, content), None);
190 self
191 }
192
193 pub async fn build(mut self) -> Result<TestEnv<E>, Error> {
195 let (repo_config, served_repo, expected_blobfs_contents, merkle) =
196 if let Some(repo_config) = self.repo_config {
197 (
199 repo_config,
200 None,
201 BTreeSet::new(),
202 Hash::from_str(
203 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
204 )
205 .expect("make merkle"),
206 )
207 } else {
208 let mut update =
211 fuchsia_pkg_testing::UpdatePackageBuilder::new(TEST_REPO_URL.parse().unwrap())
212 .packages(
213 self.packages
214 .iter()
215 .map(|p| {
216 fuchsia_url::fuchsia_pkg::PinnedAbsolutePackageUrl::new(
217 TEST_REPO_URL.parse().unwrap(),
218 p.name().clone(),
219 None,
220 *p.hash(),
221 )
222 })
223 .collect::<Vec<_>>(),
224 )
225 .firmware_images(self.firmware_images);
226 if let Some((zbi, vbmeta)) = self.fuchsia_image {
227 update = update.fuchsia_image(zbi, vbmeta);
228 }
229 if let Some((zbi, vbmeta)) = self.recovery_image {
230 update = update.recovery_image(zbi, vbmeta);
231 }
232 let (update, images) = update.build().await;
233
234 let expected_blobfs_contents = self
236 .packages
237 .iter()
238 .chain([update.as_package()])
239 .flat_map(|p| p.list_blobs())
240 .collect();
241
242 let repo = Arc::new(
243 self.packages
244 .iter()
245 .chain([update.as_package(), &images])
246 .fold(
247 RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH)
248 .add_package(update.as_package()),
249 |repo, package| repo.add_package(package),
250 )
251 .build()
252 .await
253 .expect("build repo"),
254 );
255
256 let served_repo = Arc::clone(&repo).server().start().expect("serve repo");
257 let config = RepositoryConfigs::Version1(vec![
258 served_repo.make_repo_config(TEST_REPO_URL.parse().expect("make repo config")),
259 ]);
260
261 let update_merkle = *update.as_package().hash();
262 let mut packages = vec![update.into_package()];
265 packages.append(&mut self.packages);
266 (config, Some(served_repo), expected_blobfs_contents, update_merkle)
267 };
268
269 let dir = tempfile::tempdir()?;
270 let mut path = dir.path().to_owned();
271 path.push("repo_config.json");
272 let path = path.as_path();
273 let mut file =
274 std::io::BufWriter::new(std::fs::File::create(path).context("creating file")?);
275 serde_json::to_writer(&mut file, &repo_config).unwrap();
276 file.flush().unwrap();
277
278 Ok(TestEnv {
279 blobfs: self.blobfs,
280 board: self.board,
281 channel: self.channel,
282 omaha: self.omaha,
283 expected_blobfs_contents,
284 paver: Arc::new(self.paver.build()),
285 _repo: served_repo,
286 repo_config_dir: dir,
287 update_merkle: merkle,
288 version: self.version,
289 test_executor: self.test_executor.expect("test executor must be set"),
290 system_image_hash: self.system_image_hash,
291 })
292 }
293}
294
295pub struct TestEnv<E> {
296 blobfs: Option<ClientEnd<fio::DirectoryMarker>>,
297 channel: String,
298 omaha: OmahaState,
299 expected_blobfs_contents: BTreeSet<Hash>,
300 paver: Arc<MockPaverService>,
301 _repo: Option<ServedRepository>,
302 repo_config_dir: tempfile::TempDir,
303 update_merkle: Hash,
304 board: String,
305 version: String,
306 test_executor: E,
307 system_image_hash: Option<Hash>,
308}
309
310impl<E> TestEnv<E> {
311 async fn start_omaha(omaha: OmahaState, merkle: Hash) -> Result<UpdateUrlSource, Error> {
312 match omaha {
313 OmahaState::Disabled(url) => Ok(match url {
314 Some(url) => UpdateUrlSource::UpdateUrl(url),
315 None => UpdateUrlSource::UseDefault,
316 }),
317 OmahaState::Manual(cfg) => Ok(UpdateUrlSource::OmahaConfig(cfg)),
318 OmahaState::Auto(response) => {
319 let mut response = ResponseAndMetadata { response, ..Default::default() };
321 let p: Vec<_> = response.package_name.split("?hash=").collect();
322 assert_eq!(p.len(), 2);
323 response.package_name = format!("{}?hash={}", p[0], merkle);
324 let server = OmahaServerBuilder::default()
325 .responses_by_appid(
326 vec![("integration-test-appid".to_string(), response)]
327 .into_iter()
328 .collect::<ResponseMap>(),
329 )
330 .build()
331 .unwrap();
332 let addr = OmahaServer::start_and_detach(Arc::new(Mutex::new(server)), None)
333 .await
334 .context("Starting omaha server")?;
335 let config =
336 OmahaConfig { app_id: "integration-test-appid".to_owned(), server_url: addr };
337
338 Ok(UpdateUrlSource::OmahaConfig(config))
339 }
340 }
341 }
342
343 pub async fn run<R>(self) -> R
345 where
346 E: TestExecutor<R>,
347 {
348 let update_url_source =
349 Self::start_omaha(self.omaha, self.update_merkle).await.expect("Starting Omaha server");
350
351 let mut service_fs = ServiceFs::new();
352 let paver_clone = Arc::clone(&self.paver);
353 service_fs.add_fidl_service(move |stream: PaverRequestStream| {
354 fasync::Task::spawn(
355 Arc::clone(&paver_clone)
356 .run_paver_service(stream)
357 .unwrap_or_else(|e| panic!("Failed to run mock paver: {e:?}")),
358 )
359 .detach();
360 });
361
362 let (client, server) =
363 fidl::endpoints::create_endpoints::<fidl_fuchsia_io::DirectoryMarker>();
364 service_fs
365 .serve_connection(server.into_channel().into())
366 .expect("Failed to serve connection");
367 fasync::Task::spawn(service_fs.collect()).detach();
368
369 let params = TestParams {
370 blobfs: self.blobfs,
371 board: self.board,
372 channel: self.channel,
373 expected_blobfs_contents: self.expected_blobfs_contents,
374 paver: self.paver,
375 repo_config_dir: self.repo_config_dir,
376 update_merkle: self.update_merkle,
377 version: self.version,
378 update_url_source,
379 paver_connector: client,
380 system_image_hash: self.system_image_hash,
381 };
382
383 self.test_executor.run(params).await
384 }
385}