| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::Error; |
| use async_trait::async_trait; |
| use blobfs_ramdisk::BlobfsRamdisk; |
| use fidl::endpoints::{ClientEnd, Proxy}; |
| use fidl_fuchsia_buildinfo as fbi; |
| use fidl_fuchsia_fshost as ffsh; |
| use fidl_fuchsia_io as fio; |
| use fidl_fuchsia_paver::{Asset, Configuration}; |
| use fidl_fuchsia_recovery_ui as frui; |
| use fuchsia_async as fasync; |
| use fuchsia_component::server::ServiceFs; |
| use fuchsia_component_test::{ |
| Capability, ChildOptions, ChildRef, LocalComponentHandles, RealmBuilder, Ref, Route, |
| }; |
| use fuchsia_pkg_testing::PackageBuilder; |
| use futures::{channel::mpsc, FutureExt, StreamExt, TryStreamExt}; |
| use isolated_ota::{OmahaConfig, UpdateUrlSource}; |
| use isolated_ota_env::expose_mock_paver; |
| use isolated_ota_env::{OmahaState, TestEnvBuilder, TestExecutor, TestParams}; |
| use mock_omaha_server::OmahaResponse; |
| use mock_paver::{MockPaverService, PaverEvent}; |
| use ota_lib::config::{JsonUpdateConfig, JsonUpdateType}; |
| use std::{collections::BTreeSet, sync::Arc}; |
| use vfs::{directory::entry_container::Directory, file::vmo::read_only}; |
| |
| type ProgressRendererSender = mpsc::Sender<frui::ProgressRendererRender2Request>; |
| |
| /// Represents the result of running a [`isolated_ota_env::TestEnv`]. This type is vended by |
| /// [`OtaComponentTestExecutor`] which is invoked by `TestEnv`. |
| /// |
| /// `packages` is expected to be populated from the `TestParams` given to |
| /// `OtaComponentTestExecutor`. It is expected that all blobs of all packages contained within are |
| /// also stored within `blobfs` after a successful update. |
| /// |
| /// `progress_renderer_requests` is expected to contain FIDL messages sent by the OTA component |
| /// during the execution of the test. |
| /// |
| /// `paver` is expected to be populated from `TestParams`. |
| struct TestResult { |
| blobfs: BlobfsRamdisk, |
| expected_blobfs_contents: BTreeSet<fuchsia_hash::Hash>, |
| pub progress_renderer_requests: Vec<frui::ProgressRendererRender2Request>, |
| pub paver: Arc<MockPaverService>, |
| } |
| |
| impl TestResult { |
| /// Asserts that all blobs in all the packages that were part of the Update |
| /// have been installed into the blobfs, and that the blobfs contains no extra blobs. |
| pub fn check_packages(&self) { |
| let actual_contents = self.blobfs.list_blobs().expect("Listing blobfs blobs"); |
| assert_eq!(actual_contents, self.expected_blobfs_contents); |
| } |
| } |
| |
| struct OtaComponentTestExecutor {} |
| |
| impl OtaComponentTestExecutor { |
| fn new() -> Box<Self> { |
| Box::new(Self {}) |
| } |
| } |
| |
| /// The [`TestExecutor`] for recovery OTA component integration tests is responsible for creating |
| /// the sandbox in which the OTA component runs. [`isolated_ota_env::TestEnv`] receives this object |
| /// and spins up some external dependencies for the test environment including: |
| /// - Mock TUF repository - provided by the [`fuchsia-pkg-testing`] library |
| /// - Mock Omaha server - provided by the [`mock-omaha-server`] library |
| /// - Mock fuchsia.paver.Paver FIDL service - provided by the [`mock-paver`] library |
| #[async_trait(?Send)] |
| impl TestExecutor<Result<TestResult, Error>> for OtaComponentTestExecutor { |
| /// Runs [`OtaComponentTestExecutor`] which sets up the RealmBuilder environment and triggers |
| /// an Omaha update check. |
| /// |
| /// Recovery OTA has numerous dependencies that must be mocked and/or routed including: |
| /// - FIDL protocols from this test's parent, `test_manager`. |
| /// - A directory where blobs are written - handled by [`blobfs_ramdisk::BlobfsRamdisk`]. |
| /// - Resources provided by [`isolated_ota_env::TestEnv`] which includes the following: |
| /// - SSL certificates directory |
| /// - Omaha configs - server URL and test environment app ID |
| /// - TUF repository parameters - packages, configs, and update merkle |
| /// - `fuchsia.paver.Paver` service directory |
| /// |
| /// The future returned by this function will not resolve until the OTA component sends a |
| /// Render2 FIDL request with non-Active status via `fuchsia.recovery.ui.ProgressRenderer`. |
| async fn run(&self, params: TestParams) -> Result<TestResult, Error> { |
| let builder = RealmBuilder::new().await.unwrap(); |
| |
| let system_recovery_ota_child = create_system_recovery_ota_child(&builder).await; |
| route_capabilities_from_parent(&builder, &system_recovery_ota_child).await; |
| route_ssl_certs(&builder, &system_recovery_ota_child, params.ssl_certs).await; |
| |
| if let UpdateUrlSource::OmahaConfig(omaha_config) = params.update_url_source { |
| route_config_data( |
| &builder, |
| &system_recovery_ota_child, |
| omaha_config, |
| params.channel, |
| params.version, |
| params.repo_config_dir, |
| ) |
| .await; |
| } |
| |
| let blobfs = BlobfsRamdisk::start().await.expect("launching blobfs"); |
| let progress_rx = route_and_serve_local_mocks( |
| &builder, |
| &system_recovery_ota_child, |
| params.paver_connector, |
| params.board, |
| blobfs.root_dir_handle().expect("failed getting blobfs root handle"), |
| ) |
| .await; |
| |
| let progress_renderer_requests = launch_ota_and_await_result(builder, progress_rx).await?; |
| |
| Ok(TestResult { |
| blobfs, |
| expected_blobfs_contents: params.expected_blobfs_contents, |
| paver: params.paver, |
| progress_renderer_requests, |
| }) |
| } |
| } |
| |
| async fn create_system_recovery_ota_child(builder: &RealmBuilder) -> ChildRef { |
| let system_recovery_ota_child = builder |
| .add_child( |
| "system_recovery_ota", |
| "#meta/system_recovery_ota.cm", |
| ChildOptions::new().eager(), |
| ) |
| .await |
| .expect("failed to add system_recovery_ota child"); |
| system_recovery_ota_child |
| } |
| |
| async fn route_capabilities_from_parent( |
| builder: &RealmBuilder, |
| system_recovery_ota_child: &ChildRef, |
| ) { |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::storage("tmp")) |
| .from(Ref::parent()) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to add system_recovery_ota route"); |
| |
| for protocol in [ |
| "fuchsia.boot.WriteOnlyLog", |
| "fuchsia.diagnostics.ArchiveAccessor", |
| "fuchsia.logger.LogSink", |
| "fuchsia.net.name.Lookup", |
| "fuchsia.posix.socket.Provider", |
| "fuchsia.process.Launcher", |
| ] { |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name(protocol)) |
| .from(Ref::parent()) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to add routes from parent"); |
| } |
| } |
| |
| async fn route_ssl_certs( |
| builder: &RealmBuilder, |
| system_recovery_ota_child: &ChildRef, |
| ssl_certs: fio::DirectoryProxy, |
| ) { |
| let out_dir = vfs::pseudo_directory! { |
| "ssl" => vfs::remote::remote_dir(ssl_certs), |
| }; |
| let test_ssl_certs_child = builder |
| .add_local_child( |
| "test_ssl_certs", |
| move |handles| { |
| let scope = vfs::execution_scope::ExecutionScope::new(); |
| out_dir.clone().open( |
| scope.clone(), |
| fio::OpenFlags::RIGHT_READABLE, |
| vfs::path::Path::dot(), |
| handles.outgoing_dir.into_channel().into(), |
| ); |
| async move { Ok(scope.wait().await) }.boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await |
| .expect("failed to add test_ssl_certs child"); |
| builder |
| .add_route( |
| Route::new() |
| .capability( |
| Capability::directory("root-ssl-certificates") |
| .path("/ssl") |
| .rights(fio::R_STAR_DIR), |
| ) |
| .from(&test_ssl_certs_child) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to route test_ssl_certs dir"); |
| } |
| |
| /// Routes dynamically generated config data to the OTA component. |
| /// |
| /// The OTA component requires two files to appear in its namespace at these exact locations: |
| /// - /config/data/recovery-config.json - used to set the behavior of the OTA component including |
| /// how and from where the over-the-air update is downloaded. In this case, the OTA component is |
| /// fetching an update from an Omaha server hosted by [`isolated_ota_env::TestEnv`] which provides |
| /// the server and App ID within `omaha_config`, `channel`, and `version` string. |
| /// - /config/data/ota-configs/ota_config.json - used by Omaha and TUF libraries to route package |
| /// requests to the appropriate TUF repository. In this case, the appropriate TUF repo is a mock |
| /// TUF repository run by [`isolated_ota_env::TestEnv`] which provides the configurations inside of |
| /// `repo_config_dir`. The format of this file is determined by `RepositoryConfigs` within |
| /// //src/sys/lib/fidl-fuchsia-pkg-ext/src/repo.rs. |
| async fn route_config_data( |
| builder: &RealmBuilder, |
| system_recovery_ota_child: &ChildRef, |
| omaha_config: OmahaConfig, |
| channel: String, |
| version: String, |
| repo_config_dir: tempfile::TempDir, |
| ) { |
| let recovery_config = serde_json::to_vec(&JsonUpdateConfig { |
| default_channel: channel, |
| update_type: JsonUpdateType::Omaha(omaha_config.app_id, Some(omaha_config.server_url)), |
| override_version: Some(version), |
| }) |
| .unwrap(); |
| |
| let mut repo_config_dir_path = repo_config_dir.path().to_owned(); |
| repo_config_dir_path.push("repo_config.json"); |
| let ota_config = std::fs::read(repo_config_dir_path).expect("failed to read repo_config.json"); |
| |
| let out_dir = vfs::pseudo_directory! { |
| "config" => vfs::pseudo_directory! { |
| "recovery-config.json" => read_only(recovery_config), |
| "ota-configs" => vfs::pseudo_directory! { |
| "ota_config.json" => read_only(ota_config) |
| }, |
| }, |
| }; |
| |
| let fake_config_data_child = builder |
| .add_local_child( |
| "fake_config_data", |
| move |handles| { |
| let scope = vfs::execution_scope::ExecutionScope::new(); |
| out_dir.clone().open( |
| scope.clone(), |
| fio::OpenFlags::RIGHT_READABLE, |
| vfs::path::Path::dot(), |
| handles.outgoing_dir.into_channel().into(), |
| ); |
| async move { Ok(scope.wait().await) }.boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await |
| .expect("failed to add fake_config_data child"); |
| |
| builder |
| .add_route( |
| Route::new() |
| .capability( |
| Capability::directory("config-data").path("/config").rights(fio::R_STAR_DIR), |
| ) |
| .from(&fake_config_data_child) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to route fake_config_data dir"); |
| } |
| |
| async fn route_and_serve_local_mocks( |
| builder: &RealmBuilder, |
| system_recovery_ota_child: &ChildRef, |
| paver_connector: ClientEnd<fio::DirectoryMarker>, |
| board: String, |
| blobfs_handle: ClientEnd<fio::DirectoryMarker>, |
| ) -> mpsc::Receiver<frui::ProgressRendererRender2Request> { |
| let (progress_tx, progress_rx) = mpsc::channel(100); |
| |
| let progress_renderer_server = builder |
| .add_local_child( |
| "progress_renderer", |
| move |handles: LocalComponentHandles| { |
| progress_renderer_server_mock(handles, progress_tx.clone()).boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await |
| .expect("failed to add progress_renderer child"); |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.recovery.ui.ProgressRenderer")) |
| .from(&progress_renderer_server) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to route progress_renderer protocol"); |
| |
| let build_info_server = builder |
| .add_local_child( |
| "build_info", |
| move |handles: LocalComponentHandles| { |
| build_info_server_mock(handles, board.clone()).boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await |
| .expect("failed to add build_info child"); |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.buildinfo.Provider")) |
| .from(&build_info_server) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to route build_info protocol"); |
| |
| let paver_dir_proxy = |
| paver_connector.into_proxy().expect("failed to convert paver dir client end to proxy"); |
| let paver_child = builder |
| .add_local_child( |
| "paver", |
| move |handles: LocalComponentHandles| { |
| expose_mock_paver( |
| handles, |
| fuchsia_fs::directory::clone_no_describe(&paver_dir_proxy, None).unwrap(), |
| ) |
| .boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await |
| .expect("failed to add paver child"); |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.paver.Paver")) |
| .from(&paver_child) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to route paver protocol"); |
| |
| let blobfs_proxy = fio::DirectoryProxy::from_channel(fasync::Channel::from_channel( |
| blobfs_handle.into_channel(), |
| )); |
| |
| let fshost_admin_server = builder |
| .add_local_child( |
| "fshost_admin", |
| move |handles: LocalComponentHandles| { |
| fshost_admin_server_mock( |
| handles, |
| fuchsia_fs::directory::clone_no_describe(&blobfs_proxy, None).unwrap(), |
| ) |
| .boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await |
| .expect("failed to add fshost_admin child"); |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.fshost.Admin")) |
| .from(&fshost_admin_server) |
| .to(system_recovery_ota_child), |
| ) |
| .await |
| .expect("failed to route fshost_admin protocol"); |
| |
| progress_rx |
| } |
| |
| async fn launch_ota_and_await_result( |
| builder: RealmBuilder, |
| mut progress_rx: mpsc::Receiver<frui::ProgressRendererRender2Request>, |
| ) -> Result<Vec<frui::ProgressRendererRender2Request>, Error> { |
| let realm = builder.build().await?; |
| |
| let mut progress_renderer_requests = Vec::new(); |
| loop { |
| let actual_event = progress_rx.next().await.unwrap(); |
| progress_renderer_requests.push(actual_event); |
| |
| match progress_renderer_requests.last() { |
| Some(frui::ProgressRendererRender2Request { status: Some(s), .. }) |
| if *s != frui::Status::Active => |
| { |
| break; |
| } |
| _ => continue, |
| } |
| } |
| |
| // Dropping realm will not wait for the destroy process to complete which may cause |
| // data races. Destroy the realm to release shared resources and to prevent read/write conflicts |
| // during test validation. |
| realm.destroy().await.expect("failed to destroy OTA component"); |
| |
| Ok(progress_renderer_requests) |
| } |
| |
| async fn progress_renderer_server_mock( |
| handles: LocalComponentHandles, |
| sender: ProgressRendererSender, |
| ) -> Result<(), Error> { |
| let mut fs = ServiceFs::new(); |
| |
| fs.dir("svc").add_fidl_service(move |mut stream: frui::ProgressRendererRequestStream| { |
| let mut sender = sender.clone(); |
| fasync::Task::local(async move { |
| while let Some(request) = |
| stream.try_next().await.expect("failed to serve progress_renderer service") |
| { |
| if let frui::ProgressRendererRequest::Render2 { payload, responder } = request { |
| sender.start_send(payload).expect("Failed to send render2 payload"); |
| responder.send().expect("Error replying to progress update"); |
| } |
| } |
| }) |
| .detach(); |
| }); |
| |
| // Run the ServiceFs on the outgoing directory handle from the mock handles |
| fs.serve_connection(handles.outgoing_dir).expect("failed to serve progress renderer server"); |
| fs.collect::<()>().await; |
| |
| Ok(()) |
| } |
| |
| async fn build_info_server_mock( |
| handles: LocalComponentHandles, |
| board: String, |
| ) -> Result<(), Error> { |
| let mut fs = ServiceFs::new(); |
| |
| fs.dir("svc").add_fidl_service(move |mut stream: fbi::ProviderRequestStream| { |
| let board = board.clone(); |
| fasync::Task::local(async move { |
| while let Some(request) = |
| stream.try_next().await.expect("failed to serve build_info service") |
| { |
| let fbi::ProviderRequest::GetBuildInfo { responder } = request; |
| responder |
| .send(&fbi::BuildInfo { |
| product_config: Some("test_recovery".to_string()), |
| board_config: Some(board.clone()), |
| |
| // override_version will be used instead of this, |
| // but it still must be set for the component to work. |
| version: Some(String::new()), |
| ..Default::default() |
| }) |
| .expect("Error sending buildinfo response."); |
| } |
| }) |
| .detach(); |
| }); |
| |
| // Run the ServiceFs on the outgoing directory handle from the mock handles |
| fs.serve_connection(handles.outgoing_dir).expect("failed to serve build info server"); |
| fs.collect::<()>().await; |
| Ok(()) |
| } |
| |
| async fn fshost_admin_server_mock( |
| handles: LocalComponentHandles, |
| blobfs_proxy: fio::DirectoryProxy, |
| ) -> Result<(), Error> { |
| let mut fs = ServiceFs::new(); |
| |
| fs.dir("svc").add_fidl_service(move |mut stream: ffsh::AdminRequestStream| { |
| let blobfs_proxy = fuchsia_fs::directory::clone_no_describe(&blobfs_proxy, None) |
| .expect("failed to clone blobfs proxy"); |
| fasync::Task::local(async move { |
| while let Some(request) = |
| stream.try_next().await.expect("failed to serve fshost_admin service") |
| { |
| if let ffsh::AdminRequest::WipeStorage { blobfs_root, blob_creator: _, responder } = |
| request |
| { |
| fuchsia_fs::directory::clone_onto_no_describe( |
| &blobfs_proxy, |
| None, |
| blobfs_root.unwrap(), |
| ) |
| .expect("failed to clone blobfs proxy"); |
| responder.send(Ok(())).expect("Error replying to wipe storage request"); |
| } |
| } |
| }) |
| .detach(); |
| }); |
| |
| // Run the ServiceFs on the outgoing directory handle from the mock handles |
| fs.serve_connection(handles.outgoing_dir).expect("failed to serve fshost admin server"); |
| fs.collect::<()>().await; |
| |
| Ok(()) |
| } |
| |
| async fn add_test_packages<T>(mut builder: TestEnvBuilder<T>) -> TestEnvBuilder<T> { |
| for i in 0i64..3 { |
| let name = format!("test-package{}", i); |
| let package = PackageBuilder::new(name) |
| .add_resource_at( |
| format!("data/my-package-data-{}", i), |
| format!("This is some test data for test package {}", i).as_bytes(), |
| ) |
| .add_resource_at("bin/binary", "#!/boot/bin/sh\necho Hello".as_bytes()) |
| .build() |
| .await |
| .expect("failed to build package"); |
| builder = builder.add_package(package); |
| } |
| builder |
| } |
| |
| #[fuchsia::test] |
| async fn test_ota_component_successfully_updates_with_empty_blobfs() -> Result<(), Error> { |
| let mut builder = TestEnvBuilder::new() |
| .test_executor(OtaComponentTestExecutor::new()) |
| .omaha_state(OmahaState::Auto(OmahaResponse::Update)) |
| .fuchsia_image(b"This is a zbi".to_vec(), Some(b"This is a vbmeta".to_vec())) |
| .recovery_image(b"This is recovery".to_vec(), Some(b"This is another vbmeta".to_vec())) |
| .firmware_image("".to_owned(), b"This is a bootloader upgrade".to_vec()) |
| .firmware_image("test".to_owned(), b"This is the test firmware".to_vec()); |
| builder = add_test_packages(builder).await; |
| let env = builder.build().await.expect("failed to build TestEnv"); |
| |
| let result = env.run().await.expect("failed to run TestEnv"); |
| result.check_packages(); |
| |
| assert!(result.progress_renderer_requests.len() >= 2); |
| assert_eq!( |
| *result.progress_renderer_requests.first().unwrap(), |
| frui::ProgressRendererRender2Request { |
| status: Some(frui::Status::Active), |
| percent_complete: Some(0.0), |
| ..Default::default() |
| } |
| ); |
| assert_eq!( |
| *result.progress_renderer_requests.last().unwrap(), |
| frui::ProgressRendererRender2Request { |
| status: Some(frui::Status::Complete), |
| percent_complete: Some(100.0), |
| ..Default::default() |
| } |
| ); |
| |
| assert_eq!( |
| result.paver.take_events(), |
| vec![ |
| PaverEvent::QueryCurrentConfiguration, |
| PaverEvent::ReadAsset { |
| configuration: Configuration::A, |
| asset: Asset::VerifiedBootMetadata |
| }, |
| PaverEvent::ReadAsset { configuration: Configuration::A, asset: Asset::Kernel }, |
| PaverEvent::QueryCurrentConfiguration, |
| PaverEvent::QueryConfigurationStatus { configuration: Configuration::A }, |
| PaverEvent::SetConfigurationUnbootable { configuration: Configuration::B }, |
| PaverEvent::BootManagerFlush, |
| PaverEvent::ReadAsset { configuration: Configuration::B, asset: Asset::Kernel }, |
| PaverEvent::ReadAsset { configuration: Configuration::A, asset: Asset::Kernel }, |
| PaverEvent::ReadAsset { |
| configuration: Configuration::B, |
| asset: Asset::VerifiedBootMetadata |
| }, |
| PaverEvent::ReadAsset { |
| configuration: Configuration::A, |
| asset: Asset::VerifiedBootMetadata |
| }, |
| PaverEvent::ReadFirmware { configuration: Configuration::B, firmware_type: "".into() }, |
| PaverEvent::ReadFirmware { configuration: Configuration::A, firmware_type: "".into() }, |
| PaverEvent::ReadFirmware { |
| configuration: Configuration::B, |
| firmware_type: "test".into() |
| }, |
| PaverEvent::ReadFirmware { |
| configuration: Configuration::A, |
| firmware_type: "test".into() |
| }, |
| PaverEvent::WriteFirmware { |
| configuration: Configuration::B, |
| firmware_type: "".to_owned(), |
| payload: b"This is a bootloader upgrade".to_vec(), |
| }, |
| PaverEvent::WriteFirmware { |
| configuration: Configuration::B, |
| firmware_type: "test".to_owned(), |
| payload: b"This is the test firmware".to_vec(), |
| }, |
| PaverEvent::WriteAsset { |
| configuration: Configuration::B, |
| asset: Asset::Kernel, |
| payload: b"This is a zbi".to_vec(), |
| }, |
| PaverEvent::WriteAsset { |
| configuration: Configuration::B, |
| asset: Asset::VerifiedBootMetadata, |
| payload: b"This is a vbmeta".to_vec(), |
| }, |
| PaverEvent::DataSinkFlush, |
| // Note that recovery isn't written, as isolated-ota skips them. |
| PaverEvent::SetConfigurationActive { configuration: Configuration::B }, |
| PaverEvent::BootManagerFlush, |
| // This is the isolated-ota library checking to see if the paver configured ABR properly. |
| PaverEvent::QueryActiveConfiguration, |
| ] |
| ); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn test_ota_component_reports_error_when_omaha_broken() -> Result<(), Error> { |
| // When Omaha is broken, the OTA component is expected to report that as an error. |
| // The origin of this error is from within the isolated_ota library. |
| let bad_omaha_config = OmahaConfig { |
| app_id: "broken-omaha-test".to_owned(), |
| server_url: "http://does-not-exist.fuchsia.com".to_owned(), |
| }; |
| |
| let package = PackageBuilder::new("test-package") |
| .add_resource_at("data/test", "hello, world!".as_bytes()) |
| .build() |
| .await |
| .unwrap(); |
| |
| let builder = TestEnvBuilder::new() |
| .test_executor(OtaComponentTestExecutor::new()) |
| .add_package(package) |
| .fuchsia_image(b"ZBI".to_vec(), None) |
| .omaha_state(OmahaState::Manual(bad_omaha_config)); |
| |
| let env = builder.build().await.expect("failed to build TestEnv"); |
| |
| let result = env.run().await.expect("failed to run TestEnv"); |
| |
| assert!(result.progress_renderer_requests.len() >= 2); |
| assert_eq!( |
| *result.progress_renderer_requests.first().unwrap(), |
| frui::ProgressRendererRender2Request { |
| status: Some(frui::Status::Active), |
| percent_complete: Some(0.0), |
| ..Default::default() |
| } |
| ); |
| assert_eq!( |
| *result.progress_renderer_requests.last().unwrap(), |
| frui::ProgressRendererRender2Request { |
| status: Some(frui::Status::Error), |
| ..Default::default() |
| }, |
| ); |
| |
| assert_eq!(result.paver.take_events(), vec![]); |
| Ok(()) |
| } |
| |
| // TODO(b/257130699): Add more test cases to cover blobfs issues and invalid configurations. |
| // TODO(b/259882510): Add more test cases to verify boot args, config-data and build-info affect the resolved config |