blob: d0d0c0e53c102c4e9bab44093ea68934be395adf [file] [log] [blame]
// Copyright 2020 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 {
anyhow::anyhow,
fidl_fuchsia_paver::{self as paver, PaverRequestStream},
fidl_fuchsia_pkg::{PackageCacheRequestStream, PackageResolverRequestStream},
fidl_fuchsia_update::{
CheckNotStartedReason, CheckOptions, CheckingForUpdatesData, CommitStatusProviderMarker,
CommitStatusProviderProxy, ErrorCheckingForUpdateData, Initiator,
InstallationDeferralReason, InstallationDeferredData, InstallationErrorData,
InstallationProgress, InstallingData, ManagerMarker, ManagerProxy, MonitorMarker,
MonitorRequest, MonitorRequestStream, NoUpdateAvailableData, State, UpdateInfo,
},
fidl_fuchsia_update_channelcontrol::{ChannelControlMarker, ChannelControlProxy},
fidl_fuchsia_update_installer::{InstallerMarker, UpdateNotStartedReason},
fidl_fuchsia_update_installer_ext as installer, fuchsia_async as fasync,
fuchsia_component::{
client::{App, AppBuilder},
server::{NestedEnvironment, ServiceFs},
},
fuchsia_inspect::{
assert_data_tree,
reader::DiagnosticsHierarchy,
testing::{AnyProperty, TreeAssertion},
tree_assertion,
},
fuchsia_pkg_testing::{get_inspect_hierarchy, make_packages_json},
fuchsia_zircon as zx,
futures::{
channel::{mpsc, oneshot},
prelude::*,
},
matches::assert_matches,
mock_crash_reporter::{CrashReport, MockCrashReporterService, ThrottleHook},
mock_installer::MockUpdateInstallerService,
mock_omaha_server::{OmahaResponse, OmahaServer},
mock_paver::{hooks as mphooks, MockPaverService, MockPaverServiceBuilder, PaverEvent},
mock_reboot::{MockRebootService, RebootReason},
mock_resolver::MockResolverService,
mock_verifier::MockVerifierService,
parking_lot::Mutex,
serde_json::json,
std::{
fs::{self, create_dir, File},
path::PathBuf,
sync::Arc,
},
tempfile::TempDir,
};
const OMAHA_CLIENT_CMX: &str =
"fuchsia-pkg://fuchsia.com/omaha-client-integration-tests#meta/omaha-client-service-for-integration-test.cmx";
const SYSTEM_UPDATER_CMX: &str =
"fuchsia-pkg://fuchsia.com/omaha-client-integration-tests#meta/system-updater-isolated.cmx";
const SYSTEM_UPDATE_COMMITTER_CMX: &str =
"fuchsia-pkg://fuchsia.com/omaha-client-integration-tests#meta/system-update-committer.cmx";
struct Mounts {
_test_dir: TempDir,
config_data: PathBuf,
build_info: PathBuf,
}
impl Mounts {
fn new() -> Self {
let test_dir = TempDir::new().expect("create test tempdir");
let config_data = test_dir.path().join("config_data");
create_dir(&config_data).expect("create config_data dir");
let build_info = test_dir.path().join("build_info");
create_dir(&build_info).expect("create build_info dir");
Self { _test_dir: test_dir, config_data, build_info }
}
fn write_url(&self, url: impl AsRef<[u8]>) {
let url_path = self.config_data.join("omaha_url");
fs::write(url_path, url).expect("write omaha_url");
}
fn write_appid(&self, appid: impl AsRef<[u8]>) {
let appid_path = self.config_data.join("omaha_app_id");
fs::write(appid_path, appid).expect("write omaha_app_id");
}
fn write_policy_config(&self, config: impl AsRef<[u8]>) {
let config_path = self.config_data.join("policy_config.json");
fs::write(config_path, config).expect("write policy_config.json");
}
fn write_version(&self, version: impl AsRef<[u8]>) {
let version_path = self.build_info.join("version");
fs::write(version_path, version).expect("write version");
}
}
struct Proxies {
_cache: Arc<MockCache>,
resolver: Arc<MockResolverService>,
update_manager: ManagerProxy,
channel_control: ChannelControlProxy,
commit_status_provider: CommitStatusProviderProxy,
_verifier: Arc<MockVerifierService>,
}
struct TestEnvBuilder {
response: OmahaResponse,
version: String,
installer: Option<MockUpdateInstallerService>,
paver: Option<MockPaverService>,
crash_reporter: Option<MockCrashReporterService>,
}
impl TestEnvBuilder {
fn new() -> Self {
Self {
response: OmahaResponse::NoUpdate,
version: "0.1.2.3".to_string(),
installer: None,
paver: None,
crash_reporter: None,
}
}
fn response(self, response: OmahaResponse) -> Self {
Self { response, ..self }
}
fn version(self, version: impl Into<String>) -> Self {
Self { version: version.into(), ..self }
}
fn installer(self, installer: MockUpdateInstallerService) -> Self {
Self { installer: Some(installer), ..self }
}
fn paver(self, paver: MockPaverService) -> Self {
Self { paver: Some(paver), ..self }
}
fn crash_reporter(self, crash_reporter: MockCrashReporterService) -> Self {
Self { crash_reporter: Some(crash_reporter), ..self }
}
fn build(self) -> TestEnv {
let mounts = Mounts::new();
let mut system_update_committer = AppBuilder::new(SYSTEM_UPDATE_COMMITTER_CMX.to_owned());
let mut fs = ServiceFs::new();
fs.add_proxy_service::<fidl_fuchsia_logger::LogSinkMarker, _>()
.add_proxy_service::<fidl_fuchsia_posix_socket::ProviderMarker, _>()
.add_proxy_service_to::<fidl_fuchsia_update::CommitStatusProviderMarker, _>(
system_update_committer.directory_request().unwrap().clone(),
);
let server = OmahaServer::new(self.response);
let url = server.start().expect("start server");
mounts.write_url(url);
mounts.write_appid("integration-test-appid");
mounts.write_version(self.version);
mounts.write_policy_config(
json!({
"startup_delay_seconds": 9999,
})
.to_string(),
);
let paver = Arc::new(self.paver.unwrap_or_else(|| MockPaverServiceBuilder::new().build()));
fs.add_fidl_service(move |stream: PaverRequestStream| {
fasync::Task::spawn(
Arc::clone(&paver)
.run_paver_service(stream)
.unwrap_or_else(|e| panic!("error running paver service: {:#}", anyhow!(e))),
)
.detach();
});
let resolver = Arc::new(MockResolverService::new(None));
let resolver_clone = resolver.clone();
fs.add_fidl_service(move |stream: PackageResolverRequestStream| {
let resolver_clone = resolver_clone.clone();
fasync::Task::spawn(
Arc::clone(&resolver_clone)
.run_resolver_service(stream)
.unwrap_or_else(|e| panic!("error running resolver service {:#}", anyhow!(e))),
)
.detach()
});
let cache = Arc::new(MockCache::new());
let cache_clone = cache.clone();
fs.add_fidl_service(move |stream: PackageCacheRequestStream| {
fasync::Task::spawn(Arc::clone(&cache_clone).run_cache_service(stream)).detach()
});
let (send, reboot_called) = oneshot::channel();
let send = Mutex::new(Some(send));
let reboot_service = Arc::new(MockRebootService::new(Box::new(move |reason| {
assert_eq!(reason, RebootReason::SystemUpdate);
send.lock().take().unwrap().send(()).unwrap();
Ok(())
})));
fs.add_fidl_service(move |stream| {
fasync::Task::spawn(
Arc::clone(&reboot_service)
.run_reboot_service(stream)
.unwrap_or_else(|e| panic!("error running reboot service: {:#}", anyhow!(e))),
)
.detach()
});
// Set up verifier service.
let verifier = Arc::new(MockVerifierService::new(|_| Ok(())));
let verifier_clone = Arc::clone(&verifier);
fs.add_fidl_service(move |stream| {
fasync::Task::spawn(Arc::clone(&verifier_clone).run_blobfs_verifier_service(stream))
.detach()
});
// Set up crash reporter service.
let crash_reporter = Arc::new(
self.crash_reporter.unwrap_or_else(|| MockCrashReporterService::new(|_| Ok(()))),
);
let crash_reporter_clone = Arc::clone(&crash_reporter);
fs.add_fidl_service(move |stream| {
fasync::Task::spawn(
Arc::clone(&crash_reporter_clone).run_crash_reporter_service(stream),
)
.detach()
});
let nested_environment_label = Self::make_nested_environment_label();
let (system_updater, env) = match self.installer {
Some(installer) => {
let installer = Arc::new(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_nested_environment(&nested_environment_label)
.expect("nested environment to create successfully");
(SystemUpdater::Mock(installer), env)
}
None => {
let mut system_updater = AppBuilder::new(SYSTEM_UPDATER_CMX)
.add_dir_to_namespace(
"/config/build-info".into(),
File::open(&mounts.build_info).expect("open build_info"),
)
.unwrap();
fs.add_proxy_service_to::<InstallerMarker, _>(
system_updater.directory_request().unwrap().clone(),
);
let env = fs
.create_nested_environment(&nested_environment_label)
.expect("nested environment to create successfully");
(
SystemUpdater::Real(
system_updater.spawn(env.launcher()).expect("system_updater to launch"),
),
env,
)
}
};
fasync::Task::spawn(fs.collect()).detach();
let system_update_committer = system_update_committer
.spawn(env.launcher())
.expect("system-update-committer to launch");
let omaha_client = AppBuilder::new(OMAHA_CLIENT_CMX)
.add_dir_to_namespace(
"/config/data".into(),
File::open(&mounts.config_data).expect("open config_data"),
)
.unwrap()
.add_dir_to_namespace(
"/config/build-info".into(),
File::open(&mounts.build_info).expect("open build_info"),
)
.unwrap()
.spawn(env.launcher())
.expect("omaha_client to launch");
TestEnv {
_env: env,
_mounts: mounts,
proxies: Proxies {
_cache: cache,
resolver,
update_manager: omaha_client
.connect_to_protocol::<ManagerMarker>()
.expect("connect to update manager"),
channel_control: omaha_client
.connect_to_protocol::<ChannelControlMarker>()
.expect("connect to channel control"),
commit_status_provider: system_update_committer
.connect_to_protocol::<CommitStatusProviderMarker>()
.expect("connect to commit status provider"),
_verifier: verifier,
},
omaha_client,
_system_updater: system_updater,
system_update_committer,
nested_environment_label,
reboot_called,
}
}
fn make_nested_environment_label() -> String {
let mut salt = [0; 4];
zx::cprng_draw(&mut salt[..]).expect("zx_cprng_draw does not fail");
// omaha_client_integration_test_env_xxxxxxxx is too long and gets truncated.
format!("omaha_client_test_env_{}", hex::encode(&salt))
}
}
enum SystemUpdater {
Real(App),
Mock(Arc<MockUpdateInstallerService>),
}
struct TestEnv {
_env: NestedEnvironment,
_mounts: Mounts,
proxies: Proxies,
omaha_client: App,
_system_updater: SystemUpdater,
system_update_committer: App,
nested_environment_label: String,
reboot_called: oneshot::Receiver<()>,
}
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 perform_pending_reboot(&self) -> bool {
self.proxies
.update_manager
.perform_pending_reboot()
.await
.expect("make perform_pending_reboot call")
}
async fn inspect_hierarchy(&self) -> DiagnosticsHierarchy {
get_inspect_hierarchy(
&self.nested_environment_label,
"omaha-client-service-for-integration-test.cmx",
)
.await
}
async fn assert_platform_metrics(&self, children: TreeAssertion) {
assert_data_tree!(
self.inspect_hierarchy().await,
"root": contains {
"platform_metrics": contains {
"events": contains {
"capacity": 50u64,
children,
}
}
}
);
}
}
struct MockCache;
impl MockCache {
fn new() -> Self {
Self {}
}
async fn run_cache_service(self: Arc<Self>, mut stream: PackageCacheRequestStream) {
while let Some(event) = stream.try_next().await.unwrap() {
match event {
fidl_fuchsia_pkg::PackageCacheRequest::Sync { responder } => {
responder.send(&mut Ok(())).unwrap();
}
other => panic!("unsupported PackageCache request: {:?}", other),
}
}
}
}
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() -> Option<UpdateInfo> {
// TODO(fxbug.dev/47469): version_available should be `Some("0.1.2.3".to_string())` once omaha-client
// returns version_available.
Some(UpdateInfo { version_available: None, download_size: None, ..UpdateInfo::EMPTY })
}
fn progress(fraction_completed: Option<f32>) -> Option<InstallationProgress> {
Some(InstallationProgress { fraction_completed, ..InstallationProgress::EMPTY })
}
async fn omaha_client_update(mut env: TestEnv, platform_metrics: TreeAssertion) {
env.proxies
.resolver
.url("fuchsia-pkg://integration.test.fuchsia.com/update?hash=deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
.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(),
installation_progress: progress(None),
..InstallingData::EMPTY
}),
],
)
.await;
let mut last_progress: Option<InstallationProgress> = None;
let mut waiting_for_reboot = false;
while let Some(request) = stream.try_next().await.unwrap() {
let MonitorRequest::OnState { state, responder } = request;
match state {
State::InstallingUpdate(InstallingData { update, installation_progress, .. }) => {
assert_eq!(update, update_info());
assert!(!waiting_for_reboot);
if let Some(last_progress) = last_progress {
let last = last_progress.fraction_completed.unwrap();
let current =
installation_progress.as_ref().unwrap().fraction_completed.unwrap();
assert!(
last <= current,
"progress is not increasing, last: {}, current: {}",
last,
current,
);
}
last_progress = installation_progress;
}
State::WaitingForReboot(InstallingData { update, installation_progress, .. }) => {
assert_eq!(update, update_info());
assert_eq!(installation_progress, progress(Some(1.)));
assert!(!waiting_for_reboot);
waiting_for_reboot = true;
assert_matches!(env.reboot_called.try_recv(), Ok(None));
}
state => {
panic!("Unexpected state: {:?}", state);
}
}
responder.send().unwrap();
}
assert_matches!(last_progress, Some(_));
assert!(waiting_for_reboot);
env.assert_platform_metrics(platform_metrics).await;
// This will hang if reboot was not triggered.
env.reboot_called.await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_update() {
let env = TestEnvBuilder::new().response(OmahaResponse::Update).build();
omaha_client_update(
env,
tree_assertion!(
"children": {
"0": contains {
"event": "CheckingForUpdates",
},
"1": contains {
"event": "InstallingUpdate",
"target-version": "0.1.2.3",
},
"2": contains {
"event": "WaitingForReboot",
"target-version": "0.1.2.3",
}
}
),
)
.await;
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_update_progress_with_mock_installer() {
let (mut sender, receiver) = mpsc::channel(0);
let installer = MockUpdateInstallerService::builder().states_receiver(receiver).build();
let env = TestEnvBuilder::new().response(OmahaResponse::Update).installer(installer).build();
let mut stream = env.check_now().await;
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::InstallingUpdate(InstallingData {
update: update_info(),
installation_progress: progress(None),
..InstallingData::EMPTY
}),
],
)
.await;
// Send installer state and expect manager step in lockstep to make sure that event queue
// won't merge any progress.
sender.send(installer::State::Prepare).await.unwrap();
expect_states(
&mut stream,
&[State::InstallingUpdate(InstallingData {
update: update_info(),
installation_progress: progress(Some(0.0)),
..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(),
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(),
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::InstallingUpdate(InstallingData {
update: update_info(),
installation_progress: progress(Some(1.0)),
..InstallingData::EMPTY
}),
State::WaitingForReboot(InstallingData {
update: update_info(),
installation_progress: progress(Some(1.0)),
..InstallingData::EMPTY
}),
],
)
.await;
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_installation_deferred() {
let (throttle_hook, throttler) = mphooks::throttle();
let config_status_response = Arc::new(Mutex::new(Some(paver::ConfigurationStatus::Pending)));
let config_status_response_clone = Arc::clone(&config_status_response);
let env = TestEnvBuilder::new()
.paver(
MockPaverServiceBuilder::new()
.insert_hook(throttle_hook)
.insert_hook(mphooks::config_status(move |_| {
Ok(config_status_response_clone.lock().as_ref().unwrap().clone())
}))
.build(),
)
.response(OmahaResponse::Update)
.build();
// Allow the paver to emit enough events to unblock the CommitStatusProvider FIDL server, but
// few enough to guarantee the commit is still pending.
let () = throttler.emit_next_paver_events(&[
PaverEvent::QueryCurrentConfiguration,
PaverEvent::QueryConfigurationStatus { configuration: paver::Configuration::A },
]);
// The update attempt should start, but the install should be deferred b/c we're pending commit.
let mut stream = env.check_now().await;
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::InstallationDeferredByPolicy(InstallationDeferredData {
update: update_info(),
deferral_reason: Some(InstallationDeferralReason::CurrentSystemNotCommitted),
..InstallationDeferredData::EMPTY
}),
],
)
.await;
assert_matches!(stream.next().await, None);
env.assert_platform_metrics(tree_assertion!(
"children": {
"0": contains {
"event": "CheckingForUpdates",
},
"1": contains {
"event": "InstallationDeferredByPolicy",
},
}
))
.await;
// Unblock any subsequent paver requests so that the system can commit.
drop(throttler);
// Wait for system to commit.
let event_pair =
env.proxies.commit_status_provider.is_current_system_committed().await.unwrap();
assert_eq!(
fasync::OnSignals::new(&event_pair, zx::Signals::USER_0).await,
Ok(zx::Signals::USER_0)
);
// Now that the system is committed, we should be able to perform an update. Before we do the
// update, make sure QueryConfigurationStatus returns Healthy. Otherwise, the update will fail
// because the system-updater enforces the current slot is Healthy before applying an update.
assert_eq!(
config_status_response.lock().replace(paver::ConfigurationStatus::Healthy).unwrap(),
paver::ConfigurationStatus::Pending
);
omaha_client_update(
env,
tree_assertion!(
"children": {
"0": contains {
"event": "CheckingForUpdates",
},
"1": contains {
"event": "InstallationDeferredByPolicy",
},
"2": contains {
"event": "CheckingForUpdates",
},
"3": contains {
"event": "InstallingUpdate",
"target-version": "0.1.2.3",
},
"4": contains {
"event": "WaitingForReboot",
"target-version": "0.1.2.3",
}
}
),
)
.await;
}
// When the system-update-committer crashes, OMCL should crash as well.
// TODO(fxbug.dev/66760): remove this since we won't crash OMCL -- otherwise this test will hang.
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_crashes_on_commit_status_provider_error() {
// Block the paver with a throttle to ensure it never responds to the system-update-committer.
// Otherwise, the paver may try to respond (and panic) when the system-update-committer is dead.
let (throttle_hook, _throttler) = mphooks::throttle();
let mut env = TestEnvBuilder::new()
.paver(MockPaverServiceBuilder::new().insert_hook(throttle_hook).build())
.response(OmahaResponse::Update)
.build();
env.system_update_committer.kill().unwrap();
env.check_now().await;
assert!(env.omaha_client.wait().await.unwrap().exited());
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_update_error() {
let env = TestEnvBuilder::new().response(OmahaResponse::Update).build();
let mut stream = env.check_now().await;
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::InstallingUpdate(InstallingData {
update: update_info(),
installation_progress: progress(None),
..InstallingData::EMPTY
}),
],
)
.await;
let mut last_progress: Option<InstallationProgress> = None;
let mut installation_error = false;
while let Some(request) = stream.try_next().await.unwrap() {
let MonitorRequest::OnState { state, responder } = request;
match state {
State::InstallingUpdate(InstallingData { update, installation_progress, .. }) => {
assert_eq!(update, update_info());
assert!(!installation_error);
if let Some(last_progress) = last_progress {
let last = last_progress.fraction_completed.unwrap();
let current =
installation_progress.as_ref().unwrap().fraction_completed.unwrap();
assert!(
last <= current,
"progress is not increasing, last: {}, current: {}",
last,
current,
);
}
last_progress = installation_progress;
}
State::InstallationError(InstallationErrorData {
update,
installation_progress: _,
..
}) => {
assert_eq!(update, update_info());
assert!(!installation_error);
installation_error = true;
}
state => {
panic!("Unexpected state: {:?}", state);
}
}
responder.send().unwrap();
}
assert!(installation_error);
env.assert_platform_metrics(tree_assertion!(
"children": {
"0": contains {
"event": "CheckingForUpdates",
},
"1": contains {
"event": "InstallingUpdate",
"target-version": "0.1.2.3",
},
"2": contains {
"event": "InstallationError",
"target-version": "0.1.2.3",
}
}
))
.await;
assert_data_tree!(
env.inspect_hierarchy().await,
"root": contains {
"platform_metrics": contains {
"installation_error_events": contains {
"capacity": 50u64,
"children": {
"0": contains {
"event": "InstallationError",
"target-version": "0.1.2.3",
"ts": AnyProperty,
}
}
}
}
}
);
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_no_update() {
let env = TestEnvBuilder::new().build();
let mut stream = env.check_now().await;
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::NoUpdateAvailable(NoUpdateAvailableData::EMPTY),
],
)
.await;
assert_matches!(stream.next().await, None);
env.assert_platform_metrics(tree_assertion!(
"children": {
"0": contains {
"event": "CheckingForUpdates",
},
"1": contains {
"event": "NoUpdateAvailable",
},
}
))
.await;
}
async fn do_failed_update_check(env: &TestEnv) {
let mut stream = env.check_now().await;
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::ErrorCheckingForUpdate(ErrorCheckingForUpdateData::EMPTY),
],
)
.await;
assert_matches!(stream.next().await, None);
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_invalid_response() {
let env = TestEnvBuilder::new().response(OmahaResponse::InvalidResponse).build();
do_failed_update_check(&env).await;
env.assert_platform_metrics(tree_assertion!(
"children": {
"0": contains {
"event": "CheckingForUpdates",
},
"1": contains {
"event": "ErrorCheckingForUpdate",
}
}
))
.await;
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_invalid_url() {
let env = TestEnvBuilder::new().response(OmahaResponse::InvalidURL).build();
let mut stream = env.check_now().await;
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::InstallingUpdate(InstallingData {
update: update_info(),
installation_progress: progress(None),
..InstallingData::EMPTY
}),
State::InstallationError(InstallationErrorData {
update: update_info(),
installation_progress: progress(None),
..InstallationErrorData::EMPTY
}),
],
)
.await;
assert_matches!(stream.next().await, None);
env.assert_platform_metrics(tree_assertion!(
"children": {
"0": contains {
"event": "CheckingForUpdates",
},
"1": contains {
"event": "InstallingUpdate",
"target-version": "0.1.2.3",
},
"2": contains {
"event": "InstallationError",
"target-version": "0.1.2.3",
}
}
))
.await;
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_invalid_app_set() {
let env = TestEnvBuilder::new().version("invalid-version").build();
let options = CheckOptions {
initiator: Some(Initiator::User),
allow_attaching_to_existing_update_check: None,
..CheckOptions::EMPTY
};
assert_matches!(
env.proxies.update_manager.check_now(options, None).await.expect("check_now"),
Err(CheckNotStartedReason::Internal)
);
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_policy_config_inspect() {
let env = TestEnvBuilder::new().build();
// Wait for omaha client to start.
let _ = env.proxies.channel_control.get_current().await;
assert_data_tree!(
env.inspect_hierarchy().await,
"root": contains {
"policy_config": {
"periodic_interval": 60 * 60u64,
"startup_delay": 9999u64,
"retry_delay": 5 * 60u64,
"allow_reboot_when_idle": true,
}
}
);
}
#[fasync::run_singlethreaded(test)]
async fn test_omaha_client_perform_pending_reboot_after_out_of_space() {
let env = TestEnvBuilder::new().response(OmahaResponse::Update).build();
// We should be able to get the update package just fine
env.proxies
.resolver
.url("fuchsia-pkg://integration.test.fuchsia.com/update?hash=deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
.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"),
);
// ...but the system image package should fail with NO_SPACE
env.proxies
.resolver.url("fuchsia-pkg://fuchsia.com/system_image/0?hash=beefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead")
.fail(zx::Status::NO_SPACE);
let mut stream = env.check_now().await;
// Consume the initial states
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::InstallingUpdate(InstallingData {
update: update_info(),
installation_progress: progress(None),
..InstallingData::EMPTY
}),
],
)
.await;
// Monitor the installation until we get an installation error.
let mut installation_error = false;
while let Some(request) = stream.try_next().await.unwrap() {
let MonitorRequest::OnState { state, responder } = request;
match state {
State::InstallingUpdate(InstallingData { update, .. }) => {
assert_eq!(update, update_info());
}
State::InstallationError(InstallationErrorData { update, .. }) => {
assert_eq!(update, update_info());
assert!(!installation_error);
installation_error = true;
}
state => {
panic!("Unexpected state: {:?}", state);
}
}
responder.send().unwrap();
}
assert!(installation_error);
// Simulate an incoming call to PerformPendingReboot. It returns true if we're rebooting.
assert!(env.perform_pending_reboot().await);
// This will hang if reboot was not triggered.
env.reboot_called.await.unwrap();
}
/// Verifies the signature of the CrashReport is what's expected.
fn assert_signature(report: CrashReport, expected_signature: &str) {
matches::assert_matches!(
report,
CrashReport {
crash_signature: Some(signature),
program_name: Some(program),
program_uptime: Some(_),
is_fatal: Some(false),
..
} if signature == expected_signature && program == "system"
)
}
/// When we fail with an installation error, we should file a crash report.
#[fasync::run_singlethreaded(test)]
async fn test_crash_report_installation_error() {
let (hook, mut recv) = ThrottleHook::new(Ok(()));
let env = TestEnvBuilder::new()
.response(OmahaResponse::Update)
.installer(MockUpdateInstallerService::with_response(Err(
UpdateNotStartedReason::AlreadyInProgress,
)))
.crash_reporter(MockCrashReporterService::new(hook))
.build();
let mut stream = env.check_now().await;
// Consume states to get InstallationError.
expect_states(
&mut stream,
&[
State::CheckingForUpdates(CheckingForUpdatesData::EMPTY),
State::InstallingUpdate(InstallingData {
update: update_info(),
installation_progress: progress(None),
..InstallingData::EMPTY
}),
State::InstallationError(InstallationErrorData {
update: update_info(),
installation_progress: progress(None),
..InstallationErrorData::EMPTY
}),
],
)
.await;
// Observe the crash report was filed.
assert_signature(recv.next().await.unwrap(), "fuchsia-installation-error");
}
/// When we fail 5 times to check for updates, we should file a crash report.
#[fasync::run_singlethreaded(test)]
async fn test_crash_report_consecutive_failed_update_checks() {
let (hook, mut recv) = ThrottleHook::new(Ok(()));
let env = TestEnvBuilder::new()
.response(OmahaResponse::InvalidResponse)
.crash_reporter(MockCrashReporterService::new(hook))
.build();
// Failing <5 times will not yield crash reports.
do_failed_update_check(&env).await;
do_failed_update_check(&env).await;
do_failed_update_check(&env).await;
do_failed_update_check(&env).await;
assert_matches!(recv.try_next(), Err(_));
// But failing >=5 times will.
do_failed_update_check(&env).await;
assert_signature(recv.next().await.unwrap(), "fuchsia-5-consecutive-failed-update-checks");
do_failed_update_check(&env).await;
assert_signature(recv.next().await.unwrap(), "fuchsia-6-consecutive-failed-update-checks");
}