[amber][system_updater] add interaction test
Bug: PKG-706 #comment
Change-Id: I7e58b84b9f27ff1add0321da8311c0251a61cda6
diff --git a/garnet/go/src/amber/system_updater/meta/system_updater_isolated.cmx b/garnet/go/src/amber/system_updater/meta/system_updater_isolated.cmx
new file mode 100644
index 0000000..cd17414
--- /dev/null
+++ b/garnet/go/src/amber/system_updater/meta/system_updater_isolated.cmx
@@ -0,0 +1,14 @@
+{
+ "program": {
+ "binary": "bin/system_updater"
+ },
+ "sandbox": {
+ "services": [
+ "fuchsia.pkg.PackageResolver",
+ "fuchsia.cobalt.LoggerFactory",
+ "fuchsia.device.manager.Administrator",
+ "fuchsia.logger.LogSink",
+ "fuchsia.process.Launcher"
+ ]
+ }
+}
diff --git a/garnet/packages/tests/BUILD.gn b/garnet/packages/tests/BUILD.gn
index 3d863e7..d58238d 100644
--- a/garnet/packages/tests/BUILD.gn
+++ b/garnet/packages/tests/BUILD.gn
@@ -1004,6 +1004,7 @@
"//garnet/go/src/amber/system_updater:system_updater_tests",
"//garnet/packages/prod:amber",
"//garnet/tests/amberctl:amberctl-tests",
+ "//garnet/tests/system_updater:systemupdater-tests",
]
}
diff --git a/garnet/tests/system_updater/BUILD.gn b/garnet/tests/system_updater/BUILD.gn
new file mode 100644
index 0000000..ee496ae
--- /dev/null
+++ b/garnet/tests/system_updater/BUILD.gn
@@ -0,0 +1,81 @@
+# 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.
+
+import("//build/package.gni")
+import("//build/rust/rustc_library.gni")
+import("//build/test/test_package.gni")
+import("//build/testing/environments.gni")
+
+rustc_library("driver") {
+ name = "system_updater_tests"
+ edition = "2018"
+ with_unit_tests = true
+
+ deps = [
+ "//garnet/public/lib/fidl/rust/fidl",
+ "//garnet/public/rust/fuchsia-async",
+ "//garnet/public/rust/fuchsia-component",
+ "//garnet/public/rust/fuchsia-runtime",
+ "//garnet/public/rust/fuchsia-syslog",
+ "//garnet/public/rust/fuchsia-zircon",
+ "//sdk/fidl/fuchsia.amber:fuchsia.amber-rustc",
+ "//sdk/fidl/fuchsia.pkg:fuchsia.pkg-rustc",
+ "//sdk/fidl/fuchsia.sys:fuchsia.sys-rustc",
+ "//third_party/rust_crates:failure",
+ "//third_party/rust_crates:futures-preview",
+ "//third_party/rust_crates:hex",
+ "//third_party/rust_crates:log",
+ "//third_party/rust_crates:parking_lot",
+ "//third_party/rust_crates:serde",
+ "//third_party/rust_crates:serde_derive",
+ "//third_party/rust_crates:serde_json",
+ "//third_party/rust_crates:tempfile",
+ "//zircon/public/fidl/fuchsia-cobalt:fuchsia-cobalt-rustc",
+ "//zircon/public/fidl/fuchsia-device-manager:fuchsia-device-manager-rustc",
+ ]
+}
+
+test_package("systemupdater-tests") {
+ deps = [
+ ":driver",
+ "//garnet/go/src/amber:cobalt_sw_delivery_registry",
+ "//garnet/go/src/grand_unified_binary",
+ ]
+
+ binaries = [
+ {
+ name = "system_updater"
+ source = "grand_unified_binary"
+ },
+ ]
+
+ resources = [
+ {
+ path =
+ rebase_path(get_label_info(
+ "//garnet/go/src/amber:cobalt_sw_delivery_registry",
+ "target_gen_dir") + "/cobalt_sw_delivery_registry.pb")
+ dest = "cobalt_config.pb"
+ },
+ {
+ path = rebase_path("//garnet/go/src/amber/system_updater/images")
+ dest = "images"
+ },
+ ]
+
+ meta = [
+ {
+ path = rebase_path(
+ "//garnet/go/src/amber/system_updater/meta/system_updater_isolated.cmx")
+ dest = "system_updater_isolated.cmx"
+ },
+ ]
+
+ tests = [
+ {
+ name = "system_updater_tests_lib_test"
+ environments = basic_envs
+ },
+ ]
+}
diff --git a/garnet/tests/system_updater/meta/system_updater_tests_lib_test.cmx b/garnet/tests/system_updater/meta/system_updater_tests_lib_test.cmx
new file mode 100644
index 0000000..e22bf47
--- /dev/null
+++ b/garnet/tests/system_updater/meta/system_updater_tests_lib_test.cmx
@@ -0,0 +1,15 @@
+{
+ "program": {
+ "binary": "test/system_updater_tests_lib_test"
+ },
+ "sandbox": {
+ "features": [
+ "system-temp"
+ ],
+ "services": [
+ "fuchsia.sys.Environment",
+ "fuchsia.sys.Launcher",
+ "fuchsia.sys.Loader"
+ ]
+ }
+}
diff --git a/garnet/tests/system_updater/src/lib.rs b/garnet/tests/system_updater/src/lib.rs
new file mode 100644
index 0000000..ddff6cf
--- /dev/null
+++ b/garnet/tests/system_updater/src/lib.rs
@@ -0,0 +1,306 @@
+// 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.
+
+#![feature(async_await, await_macro)]
+#![cfg(test)]
+use {
+ failure::{bail, Error},
+ fidl_fuchsia_pkg::PackageResolverRequestStream,
+ fidl_fuchsia_sys::{LauncherProxy, TerminationReason},
+ fuchsia_async as fasync,
+ fuchsia_component::{
+ client::{AppBuilder, Stdio},
+ server::{NestedEnvironment, ServiceFs},
+ },
+ fuchsia_zircon::Status,
+ futures::prelude::*,
+ parking_lot::Mutex,
+ std::{fs::create_dir, fs::File, path::PathBuf, sync::Arc},
+ tempfile::TempDir,
+};
+
+struct TestEnv {
+ env: NestedEnvironment,
+ resolver: Arc<MockResolverService>,
+ reboot_service: Arc<MockRebootService>,
+ logger_factory: Arc<MockLoggerFactory>,
+ _test_dir: TempDir,
+ update_path: PathBuf,
+ blobfs_path: PathBuf,
+}
+
+impl TestEnv {
+ fn launcher(&self) -> &LauncherProxy {
+ self.env.launcher()
+ }
+
+ fn new() -> Self {
+ let mut fs = ServiceFs::new();
+ let resolver = Arc::new(MockResolverService::new());
+ let resolver_clone = resolver.clone();
+ fs.add_fidl_service(move |stream: PackageResolverRequestStream| {
+ let resolver_clone = resolver_clone.clone();
+ fasync::spawn(
+ resolver_clone
+ .run_resolver_service(stream)
+ .unwrap_or_else(|e| eprintln!("error running resolver service: {:?}", e)),
+ )
+ });
+ let reboot_service = Arc::new(MockRebootService::new());
+ let reboot_service_clone = reboot_service.clone();
+ fs.add_fidl_service(move |stream| {
+ let reboot_service_clone = reboot_service_clone.clone();
+ fasync::spawn(
+ reboot_service_clone
+ .run_reboot_service(stream)
+ .unwrap_or_else(|e| eprintln!("error running reboot service: {:?}", e)),
+ )
+ });
+ let logger_factory = Arc::new(MockLoggerFactory::new());
+ let logger_factory_cloned = logger_factory.clone();
+ fs.add_fidl_service(move |stream| {
+ let logger_factory_cloned = logger_factory_cloned.clone();
+ fasync::spawn(
+ logger_factory_cloned
+ .run_logger_factory(stream)
+ .unwrap_or_else(|e| eprintln!("error running logger factory: {:?}", e)),
+ )
+ });
+ let env = fs
+ .create_salted_nested_environment("systemupdater_env")
+ .expect("nested environment to create successfully");
+ fasync::spawn(fs.collect());
+
+ let test_dir = TempDir::new().expect("create test tempdir");
+
+ let blobfs_path = test_dir.path().join("blob");
+ create_dir(&blobfs_path).expect("create blob dir");
+
+ let update_path = test_dir.path().join("update");
+ create_dir(&update_path).expect("create update pkg dir");
+
+ Self {
+ env,
+ resolver,
+ reboot_service,
+ logger_factory,
+ _test_dir: test_dir,
+ update_path,
+ blobfs_path,
+ }
+ }
+
+ async fn run_system_updater<'a>(&'a self, args: SystemUpdaterArgs<'a>) {
+ let launcher = self.launcher();
+ let argv = ["-initiator", args.initiator, "-target", args.target];
+ let blobfs_dir = File::open(&self.blobfs_path).expect("open blob dir");
+ let update_dir = File::open(&self.update_path).expect("open update pkg dir");
+
+ let system_updater = AppBuilder::new(
+ "fuchsia-pkg://fuchsia.com/systemupdater-tests#meta/system_updater_isolated.cmx",
+ )
+ .args(argv.iter().map(|s| *s))
+ .add_dir_to_namespace("/blob".to_string(), blobfs_dir)
+ .expect("/blob to mount")
+ .add_dir_to_namespace("/pkgfs/packages/update/0".to_string(), update_dir)
+ .expect("/pkgfs/packages/update/0 to mount")
+ .stderr(Stdio::MakePipe)
+ .spawn(launcher)
+ .expect("system_updater to launch");
+
+ let output =
+ await!(system_updater.wait_with_output()).expect("no errors while waiting for exit");
+
+ assert_eq!(output.exit_status.reason(), TerminationReason::Exited);
+ println!(
+ "system_updater {:?} exited with {}. logs:\n{}\n",
+ argv,
+ output.exit_status.code(),
+ String::from_utf8_lossy(&output.stderr)
+ );
+ assert!(
+ output.exit_status.success(),
+ "system_updater {:?} exited with {}",
+ argv,
+ output.exit_status.code(),
+ );
+ }
+}
+
+struct SystemUpdaterArgs<'a> {
+ initiator: &'a str,
+ target: &'a str,
+}
+
+struct MockResolverService {
+ resolved_uris: Mutex<Vec<String>>,
+}
+
+impl MockResolverService {
+ fn new() -> Self {
+ Self { resolved_uris: Mutex::new(vec![]) }
+ }
+ async fn run_resolver_service(
+ self: Arc<Self>,
+ mut stream: PackageResolverRequestStream,
+ ) -> Result<(), Error> {
+ while let Some(event) = await!(stream.try_next())? {
+ let fidl_fuchsia_pkg::PackageResolverRequest::Resolve {
+ package_uri,
+ selectors: _,
+ update_policy: _,
+ dir: _,
+ responder,
+ } = event;
+ eprintln!("TEST: Got resolve request for {:?}", package_uri);
+ self.resolved_uris.lock().push(package_uri);
+ responder.send(Status::OK.into_raw())?;
+ }
+
+ Ok(())
+ }
+}
+
+struct MockRebootService {
+ called: Mutex<u32>,
+}
+impl MockRebootService {
+ fn new() -> Self {
+ Self { called: Mutex::new(0) }
+ }
+
+ async fn run_reboot_service(
+ self: Arc<Self>,
+ mut stream: fidl_fuchsia_device_manager::AdministratorRequestStream,
+ ) -> Result<(), Error> {
+ while let Some(event) = await!(stream.try_next())? {
+ let fidl_fuchsia_device_manager::AdministratorRequest::Suspend { flags, responder } =
+ event;
+ eprintln!("TEST: Got reboot request with flags {:?}", flags);
+ *self.called.lock() += 1;
+ responder.send(Status::OK.into_raw())?;
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
+struct CustomEvent {
+ metric_id: u32,
+ values: Vec<fidl_fuchsia_cobalt::CustomEventValue>,
+}
+
+struct MockLogger {
+ custom_events: Mutex<Vec<CustomEvent>>,
+}
+
+impl MockLogger {
+ fn new() -> Self {
+ Self { custom_events: Mutex::new(vec![]) }
+ }
+
+ async fn run_logger(
+ self: Arc<Self>,
+ mut stream: fidl_fuchsia_cobalt::LoggerRequestStream,
+ ) -> Result<(), Error> {
+ while let Some(event) = await!(stream.try_next())? {
+ match event {
+ fidl_fuchsia_cobalt::LoggerRequest::LogCustomEvent {
+ metric_id,
+ event_values,
+ responder,
+ } => {
+ eprintln!("TEST: Got Logger request with metric_id {:?}", metric_id);
+ self.custom_events.lock().push(CustomEvent { metric_id, values: event_values });
+ responder.send(fidl_fuchsia_cobalt::Status::Ok)?;
+ }
+ _ => {
+ bail!("unhandled Logger method {:?}", event);
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+struct MockLoggerFactory {
+ loggers: Mutex<Vec<Arc<MockLogger>>>,
+}
+
+impl MockLoggerFactory {
+ fn new() -> Self {
+ Self { loggers: Mutex::new(vec![]) }
+ }
+
+ async fn run_logger_factory(
+ self: Arc<Self>,
+ mut stream: fidl_fuchsia_cobalt::LoggerFactoryRequestStream,
+ ) -> Result<(), Error> {
+ while let Some(event) = await!(stream.try_next())? {
+ match event {
+ fidl_fuchsia_cobalt::LoggerFactoryRequest::CreateLogger {
+ profile,
+ logger,
+ responder,
+ } => {
+ eprintln!("TEST: Got CreateLogger request with profile {:?}", profile);
+ let mock_logger = Arc::new(MockLogger::new());
+ self.loggers.lock().push(mock_logger.clone());
+ fasync::spawn(
+ mock_logger
+ .run_logger(logger.into_stream()?)
+ .unwrap_or_else(|e| eprintln!("error while running Logger: {:?}", e)),
+ );
+ responder.send(fidl_fuchsia_cobalt::Status::Ok)?;
+ }
+ _ => {
+ bail!("unhandled LoggerFactory method: {:?}", event);
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+#[fasync::run_singlethreaded(test)]
+async fn test_system_update() {
+ let env = TestEnv::new();
+
+ std::fs::write(
+ env.update_path.join("packages"),
+ "system_image/0=42ade6f4fd51636f70c68811228b4271ed52c4eb9a647305123b4f4d0741f296\n",
+ )
+ .expect("create update/packages");
+
+ await!(env.run_system_updater(SystemUpdaterArgs { initiator: "manual", target: "m3rk13" }));
+
+ assert_eq!(*env.resolver.resolved_uris.lock(), vec![
+ "fuchsia-pkg://fuchsia.com/system_image/0?hash=42ade6f4fd51636f70c68811228b4271ed52c4eb9a647305123b4f4d0741f296"
+ ]);
+
+ let loggers = env.logger_factory.loggers.lock().clone();
+ assert_eq!(loggers.len(), 1);
+ let logger = loggers.into_iter().next().unwrap();
+ let custom_events = logger.custom_events.lock().clone();
+ assert_eq!(custom_events.len(), 1);
+ let ev = custom_events.into_iter().next().unwrap();
+ assert_eq!(ev.metric_id, 3);
+ assert_eq!(
+ ev.values.iter().filter(|v| v.dimension_name == "target").map(|v| v.value.clone()).next(),
+ Some(fidl_fuchsia_cobalt::Value::StringValue("m3rk13".into()))
+ );
+ assert_eq!(
+ ev.values
+ .iter()
+ .filter(|v| v.dimension_name == "error_code")
+ .map(|v| v.value.clone())
+ .next(),
+ Some(fidl_fuchsia_cobalt::Value::IntValue(0))
+ );
+
+ assert_eq!(*env.reboot_service.called.lock(), 1);
+}