blob: 83b97a5024bcc5d567ca6c2894c8e7a3c1295fd9 [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.
use {
anyhow::anyhow,
fidl_fuchsia_paver::{Configuration, ConfigurationStatus},
fidl_fuchsia_update::{CommitStatusProviderMarker, CommitStatusProviderProxy},
fuchsia_async::{self as fasync, OnSignals, TimeoutExt},
fuchsia_component::{
client::{App, AppBuilder},
server::{NestedEnvironment, ServiceFs},
},
fuchsia_zircon::{self as zx, AsHandleRef},
futures::prelude::*,
matches::assert_matches,
mock_paver::{hooks as mphooks, MockPaverService, MockPaverServiceBuilder, PaverEvent},
std::{sync::Arc, time::Duration},
};
const SYSTEM_UPDATE_COMMITTER_CMX: &str =
"fuchsia-pkg://fuchsia.com/system-update-committer-integration-tests#meta/system-update-committer.cmx";
const HANG_DURATION: Duration = Duration::from_millis(500);
struct TestEnvBuilder {
paver_service_builder: Option<MockPaverServiceBuilder>,
}
impl TestEnvBuilder {
fn paver_service_builder(self, paver_service_builder: MockPaverServiceBuilder) -> Self {
Self { paver_service_builder: Some(paver_service_builder), ..self }
}
fn build(self) -> TestEnv {
let mut fs = ServiceFs::new();
fs.add_proxy_service::<fidl_fuchsia_logger::LogSinkMarker, _>();
// Set up paver service.
let paver_service_builder =
self.paver_service_builder.unwrap_or_else(|| MockPaverServiceBuilder::new());
let paver_service = Arc::new(paver_service_builder.build());
let paver_service_clone = Arc::clone(&paver_service);
fs.add_fidl_service(move |stream| {
fasync::Task::spawn(
Arc::clone(&paver_service_clone)
.run_paver_service(stream)
.unwrap_or_else(|e| panic!("error running paver service: {:#}", anyhow!(e))),
)
.detach()
});
let env = fs
.create_salted_nested_environment("system_update_committer_env")
.expect("nested environment to create successfully");
fasync::Task::spawn(fs.collect()).detach();
let system_update_committer = AppBuilder::new(SYSTEM_UPDATE_COMMITTER_CMX.to_owned())
.spawn(env.launcher())
.expect("system-update-committer to launch");
TestEnv { _env: env, system_update_committer, _paver_service: paver_service }
}
}
struct TestEnv {
_env: NestedEnvironment,
system_update_committer: App,
_paver_service: Arc<MockPaverService>,
}
impl TestEnv {
fn builder() -> TestEnvBuilder {
TestEnvBuilder { paver_service_builder: None }
}
/// Opens a connection to the fuchsia.update/CommitStatusProvider FIDL service.
fn commit_status_provider_proxy(&self) -> CommitStatusProviderProxy {
self.system_update_committer.connect_to_service::<CommitStatusProviderMarker>().unwrap()
}
}
/// IsCurrentSystemCommitted should hang until when the Paver responds to QueryConfigurationStatus.
#[fasync::run_singlethreaded(test)]
async fn is_current_system_committed_hangs_until_query_configuration_status() {
let (throttle_hook, throttler) = mphooks::throttle();
let env = TestEnv::builder()
.paver_service_builder(MockPaverServiceBuilder::new().insert_hook(throttle_hook))
.build();
// No paver events yet, so the commit status FIDL server is still hanging.
// We use timeouts to tell if the FIDL server hangs. This is obviously not ideal, but
// there does not seem to be a better way of doing it. We considered using `run_until_stalled`,
// but that's no good because the system-update-committer is running in a seperate process.
assert_matches!(
env.commit_status_provider_proxy()
.is_current_system_committed()
.map(Some)
.on_timeout(HANG_DURATION, || None)
.await,
None
);
// Even after the first paver response, is_current_system_committed should still hang.
let () = throttler.emit_next_paver_event(&PaverEvent::QueryCurrentConfiguration);
assert_matches!(
env.commit_status_provider_proxy()
.is_current_system_committed()
.map(Some)
.on_timeout(HANG_DURATION, || None)
.await,
None
);
// After the second paver event, we're finally unblocked.
let () = throttler.emit_next_paver_event(&PaverEvent::QueryConfigurationStatus {
configuration: Configuration::A,
});
env.commit_status_provider_proxy().is_current_system_committed().await.unwrap();
}
/// If the current system is pending commit, the commit status FIDL server should hang
/// until the verifiers start (e.g. after we call QueryConfigurationStatus). Once the
/// verifications complete, we should observe the `USER_0` signal.
#[fasync::run_singlethreaded(test)]
async fn system_pending_commit() {
let (throttle_hook, throttler) = mphooks::throttle();
let env = TestEnv::builder()
.paver_service_builder(
MockPaverServiceBuilder::new()
.insert_hook(throttle_hook)
.insert_hook(mphooks::config_status(|_| Ok(ConfigurationStatus::Pending))),
)
.build();
// Emit the first 2 paver events to unblock the FIDL server.
let () = throttler.emit_next_paver_events(&[
PaverEvent::QueryCurrentConfiguration,
PaverEvent::QueryConfigurationStatus { configuration: Configuration::A },
]);
let event_pair =
env.commit_status_provider_proxy().is_current_system_committed().await.unwrap();
assert_eq!(
event_pair.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE_PAST),
Err(zx::Status::TIMED_OUT)
);
// Once the remaining paver calls are emitted, the system should commit.
let () = throttler.emit_next_paver_events(&[
PaverEvent::SetConfigurationHealthy { configuration: Configuration::A },
PaverEvent::SetConfigurationUnbootable { configuration: Configuration::B },
PaverEvent::BootManagerFlush,
]);
assert_eq!(OnSignals::new(&event_pair, zx::Signals::USER_0).await, Ok(zx::Signals::USER_0));
}
/// If the current system is already committed, the EventPair returned should immediately have
/// `USER_0` asserted.
#[fasync::run_singlethreaded(test)]
async fn system_already_committed() {
let (throttle_hook, throttler) = mphooks::throttle();
let env = TestEnv::builder()
.paver_service_builder(MockPaverServiceBuilder::new().insert_hook(throttle_hook))
.build();
// Emit the first 2 paver events to unblock the FIDL server.
let () = throttler.emit_next_paver_events(&[
PaverEvent::QueryCurrentConfiguration,
PaverEvent::QueryConfigurationStatus { configuration: Configuration::A },
]);
// When the commit status FIDL responds, the event pair should immediately observe the signal.
let event_pair =
env.commit_status_provider_proxy().is_current_system_committed().await.unwrap();
assert_eq!(
event_pair.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE_PAST),
Ok(zx::Signals::USER_0)
);
}
/// When the system-update-committer terminates, all clients with handles to the EventPair
/// should observe `EVENTPAIR_CLOSED`.
#[fasync::run_singlethreaded(test)]
async fn eventpair_closed() {
let env = TestEnv::builder().build();
let event_pair =
env.commit_status_provider_proxy().is_current_system_committed().await.unwrap();
drop(env);
assert_eq!(
OnSignals::new(&event_pair, zx::Signals::EVENTPAIR_CLOSED).await,
Ok(zx::Signals::EVENTPAIR_CLOSED | zx::Signals::USER_0)
);
}
/// There's some complexity with how we handle CommitStatusProvider requests. So, let's do
/// a sanity check to verify our implementation can handle multiple clients.
#[fasync::run_singlethreaded(test)]
async fn multiple_commit_status_provider_requests() {
let env = TestEnv::builder().build();
let p0 = env.commit_status_provider_proxy().is_current_system_committed().await.unwrap();
let p1 = env.commit_status_provider_proxy().is_current_system_committed().await.unwrap();
assert_eq!(
p0.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE_PAST),
Ok(zx::Signals::USER_0)
);
assert_eq!(
p1.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE_PAST),
Ok(zx::Signals::USER_0)
);
}