[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);
+}