blob: 3cdd74ee74adea3b4795bdade2d8a4260efaab9c [file] [log] [blame]
// Copyright 2019 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.
#![cfg(test)]
use {
fidl_fuchsia_paver::{Configuration, PaverRequestStream},
fidl_fuchsia_update::{
CheckOptions, CheckingForUpdatesData, Initiator, InstallationProgress, InstallingData,
ManagerMarker, ManagerProxy, MonitorMarker, MonitorRequest, MonitorRequestStream, State,
UpdateInfo,
},
fidl_fuchsia_update_channel::{ProviderMarker, ProviderProxy},
fidl_fuchsia_update_installer_ext as installer, fuchsia_async as fasync,
fuchsia_component::{
client::{App, AppBuilder},
server::{NestedEnvironment, ServiceFs},
},
fuchsia_pkg_testing::make_packages_json,
fuchsia_zircon::Status,
futures::{channel::mpsc, prelude::*},
mock_installer::MockUpdateInstallerService,
mock_paver::{MockPaverService, MockPaverServiceBuilder, PaverEvent},
mock_resolver::MockResolverService,
std::{fs::File, sync::Arc},
tempfile::TempDir,
};
const SYSTEM_UPDATE_CHECKER_CMX: &str = "fuchsia-pkg://fuchsia.com/system-update-checker-integration-tests#meta/system-update-checker-for-integration-test.cmx";
struct Mounts {
misc_ota: TempDir,
pkgfs_system: TempDir,
}
struct Proxies {
_paver: Arc<MockPaverService>,
paver_events: mpsc::UnboundedReceiver<PaverEvent>,
resolver: Arc<MockResolverService>,
channel_provider: ProviderProxy,
update_manager: ManagerProxy,
}
impl Mounts {
fn new() -> Self {
Self {
misc_ota: tempfile::tempdir().expect("/tmp to exist"),
pkgfs_system: tempfile::tempdir().expect("/tmp to exist"),
}
}
}
struct TestEnvBuilder {
paver_builder: MockPaverServiceBuilder,
paver_events: mpsc::UnboundedReceiver<PaverEvent>,
installer: MockUpdateInstallerService,
}
impl TestEnvBuilder {
fn new() -> Self {
let (events_tx, events_rx) = mpsc::unbounded();
let paver_builder = MockPaverServiceBuilder::new().event_hook(move |event| {
events_tx.unbounded_send(event.to_owned()).expect("to write to events channel")
});
Self {
paver_builder,
paver_events: events_rx,
installer: MockUpdateInstallerService::builder().build(),
}
}
fn paver_init<F>(self, paver_init: F) -> Self
where
F: FnOnce(MockPaverServiceBuilder) -> MockPaverServiceBuilder,
{
Self { paver_builder: paver_init(self.paver_builder), ..self }
}
fn installer(self, installer: MockUpdateInstallerService) -> Self {
Self { installer, ..self }
}
fn build(self) -> TestEnv {
let mounts = Mounts::new();
std::fs::write(
mounts.pkgfs_system.path().join("meta"),
"0000000000000000000000000000000000000000000000000000000000000001",
)
.expect("write pkgfs/system/meta");
let mut fs = ServiceFs::new();
fs.add_proxy_service::<fidl_fuchsia_logger::LogSinkMarker, _>();
let paver = self.paver_builder.build();
let paver = Arc::new(paver);
let paver_clone = Arc::clone(&paver);
fs.add_fidl_service(move |stream: PaverRequestStream| {
fasync::Task::spawn(
Arc::clone(&paver_clone)
.run_paver_service(stream)
.unwrap_or_else(|e| panic!("Failed to run paver: {:?}", e)),
)
.detach();
});
let resolver = Arc::new(MockResolverService::new(None));
let resolver_clone = Arc::clone(&resolver);
fs.add_fidl_service(move |stream| {
fasync::Task::spawn(
Arc::clone(&resolver_clone)
.run_resolver_service(stream)
.unwrap_or_else(|e| panic!("error running resolver service {:?}", e)),
)
.detach()
});
let installer = Arc::new(self.installer);
let installer_clone = Arc::clone(&installer);
fs.add_fidl_service(move |stream| {
fasync::Task::spawn(Arc::clone(&installer_clone).run_service(stream)).detach()
});
let env = fs
.create_salted_nested_environment("system-update-checker_integration_test_env")
.expect("nested environment to create successfully");
fasync::Task::spawn(fs.collect()).detach();
let system_update_checker = AppBuilder::new(SYSTEM_UPDATE_CHECKER_CMX)
.add_dir_to_namespace(
"/misc/ota".to_string(),
File::open(mounts.misc_ota.path()).expect("/misc/ota tempdir to open"),
)
.expect("/misc/ota to mount")
.add_dir_to_namespace(
"/pkgfs/system".to_string(),
File::open(mounts.pkgfs_system.path()).expect("/pkgfs/system tempdir to open"),
)
.expect("/pkgfs/system to mount")
.spawn(env.launcher())
.expect("system_update_checker to launch");
TestEnv {
_env: env,
_mounts: mounts,
proxies: Proxies {
_paver: paver,
paver_events: self.paver_events,
resolver,
channel_provider: system_update_checker
.connect_to_service::<ProviderMarker>()
.expect("connect to channel provider"),
update_manager: system_update_checker
.connect_to_service::<ManagerMarker>()
.expect("connect to update manager"),
},
_system_update_checker: system_update_checker,
}
}
}
struct TestEnv {
_env: NestedEnvironment,
_mounts: Mounts,
proxies: Proxies,
_system_update_checker: App,
}
impl TestEnv {
async fn check_now(&self) -> MonitorRequestStream {
let options = CheckOptions {
initiator: Some(Initiator::User),
allow_attaching_to_existing_update_check: Some(false),
..CheckOptions::empty()
};
let (client_end, stream) =
fidl::endpoints::create_request_stream::<MonitorMarker>().unwrap();
self.proxies
.update_manager
.check_now(options, Some(client_end))
.await
.expect("make check_now call")
.expect("check started");
stream
}
}
async fn expect_states(stream: &mut MonitorRequestStream, expected_states: &[State]) {
for expected_state in expected_states {
let MonitorRequest::OnState { state, responder } =
stream.try_next().await.unwrap().unwrap();
assert_eq!(&state, expected_state);
responder.send().unwrap();
}
}
fn update_info(download_size: Option<u64>) -> Option<UpdateInfo> {
Some(UpdateInfo {
version_available: Some(
"beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead".to_string(),
),
download_size,
..UpdateInfo::empty()
})
}
fn progress(fraction_completed: Option<f32>) -> Option<InstallationProgress> {
Some(InstallationProgress { fraction_completed, ..InstallationProgress::empty() })
}
#[fasync::run_singlethreaded(test)]
// Test will hang if system-update-checker does not call paver service
async fn test_calls_paver_service() {
let env = TestEnvBuilder::new().build();
assert_eq!(
env.proxies.paver_events.take(2).collect::<Vec<PaverEvent>>().await,
vec![
PaverEvent::QueryCurrentConfiguration,
PaverEvent::QueryConfigurationStatus { configuration: Configuration::A }
]
);
}
#[fasync::run_singlethreaded(test)]
// Test will hang if system-update-checker does not call paver service
async fn test_channel_provider_get_current_works_after_paver_service_fails() {
let env = TestEnvBuilder::new().paver_init(|p| p.call_hook(|_| Status::INTERNAL)).build();
assert_eq!(
env.proxies.paver_events.take(1).collect::<Vec<PaverEvent>>().await,
vec![PaverEvent::QueryCurrentConfiguration]
);
assert_eq!(
env.proxies.channel_provider.get_current().await.expect("get_current"),
"".to_string()
);
}
#[fasync::run_singlethreaded(test)]
// Test will hang if system-update-checker does not call paver service
async fn test_update_manager_check_now_works_after_paver_service_fails() {
let env = TestEnvBuilder::new().paver_init(|p| p.call_hook(|_| Status::INTERNAL)).build();
assert_eq!(
env.proxies.paver_events.take(1).collect::<Vec<PaverEvent>>().await,
vec![PaverEvent::QueryCurrentConfiguration]
);
let (client_end, request_stream) =
fidl::endpoints::create_request_stream().expect("create_request_stream");
assert!(env
.proxies
.update_manager
.check_now(
fidl_fuchsia_update::CheckOptions {
initiator: Some(fidl_fuchsia_update::Initiator::User),
allow_attaching_to_existing_update_check: Some(true),
..fidl_fuchsia_update::CheckOptions::empty()
},
Some(client_end),
)
.await
.is_ok());
assert_eq!(
request_stream
.map(|r| {
let MonitorRequest::OnState { state, responder } = r.unwrap();
responder.send().unwrap();
state
})
.collect::<Vec<State>>()
.await,
vec![
State::CheckingForUpdates(fidl_fuchsia_update::CheckingForUpdatesData::empty()),
State::ErrorCheckingForUpdate(fidl_fuchsia_update::ErrorCheckingForUpdateData::empty()),
]
);
}
#[fasync::run_singlethreaded(test)]
async fn test_update_manager_progress() {
let (mut sender, receiver) = mpsc::channel(0);
let installer = MockUpdateInstallerService::builder().states_receiver(receiver).build();
let env = TestEnvBuilder::new().installer(installer).build();
env.proxies.resolver.url("fuchsia-pkg://fuchsia.com/update/0").resolve(
&env.proxies
.resolver
.package("update", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
.add_file(
"packages.json",
make_packages_json(["fuchsia-pkg://fuchsia.com/system_image/0?hash=beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead"]),
)
.add_file("zbi", "fake zbi"),
);
env.proxies
.resolver.url("fuchsia-pkg://fuchsia.com/system_image/0?hash=beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead")
.resolve(
&env.proxies
.resolver
.package("system_image", "beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeada")
);
let mut stream = env.check_now().await;
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::empty()),
State::InstallingUpdate(InstallingData {
update: update_info(None),
installation_progress: None,
..InstallingData::empty()
}),
],
)
.await;
sender.send(installer::State::Prepare).await.unwrap();
expect_states(
&mut stream,
&[State::InstallingUpdate(InstallingData {
update: update_info(None),
installation_progress: progress(None),
..InstallingData::empty()
})],
)
.await;
let installer_update_info = installer::UpdateInfo::builder().download_size(1000).build();
sender
.send(installer::State::Fetch(
installer::UpdateInfoAndProgress::new(
installer_update_info,
installer::Progress::none(),
)
.unwrap(),
))
.await
.unwrap();
expect_states(
&mut stream,
&[State::InstallingUpdate(InstallingData {
update: update_info(Some(1000)),
installation_progress: progress(Some(0.0)),
..InstallingData::empty()
})],
)
.await;
sender
.send(installer::State::Stage(
installer::UpdateInfoAndProgress::new(
installer_update_info,
installer::Progress::builder()
.fraction_completed(0.5)
.bytes_downloaded(500)
.build(),
)
.unwrap(),
))
.await
.unwrap();
expect_states(
&mut stream,
&[State::InstallingUpdate(InstallingData {
update: update_info(Some(1000)),
installation_progress: progress(Some(0.5)),
..InstallingData::empty()
})],
)
.await;
sender
.send(installer::State::WaitToReboot(installer::UpdateInfoAndProgress::done(
installer_update_info,
)))
.await
.unwrap();
expect_states(
&mut stream,
&[State::WaitingForReboot(InstallingData {
update: update_info(Some(1000)),
installation_progress: progress(Some(1.0)),
..InstallingData::empty()
})],
)
.await;
}