[system-updater] Remove oneshot support.

Fixed: 55414

Change-Id: Idef138822a011ad3c9fa49d0948ddd96adcb6c8a
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/441850
Commit-Queue: Sen Jiang <senj@google.com>
Reviewed-by: Kevin Wells <kevinwells@google.com>
Testability-Review: Kevin Wells <kevinwells@google.com>
diff --git a/src/sys/pkg/bin/system-updater/BUILD.gn b/src/sys/pkg/bin/system-updater/BUILD.gn
index c8127b9..b17aa4e 100644
--- a/src/sys/pkg/bin/system-updater/BUILD.gn
+++ b/src/sys/pkg/bin/system-updater/BUILD.gn
@@ -38,7 +38,6 @@
     "//src/sys/pkg/lib/update-package",
     "//third_party/cobalt/src/lib/client/rust:cobalt-client",
     "//third_party/rust_crates:anyhow",
-    "//third_party/rust_crates:argh",
     "//third_party/rust_crates:async-trait",
     "//third_party/rust_crates:chrono",
     "//third_party/rust_crates:futures",
@@ -60,7 +59,6 @@
   ]
 
   sources = [
-    "src/args.rs",
     "src/fidl.rs",
     "src/install_manager.rs",
     "src/main.rs",
diff --git a/src/sys/pkg/bin/system-updater/src/args.rs b/src/sys/pkg/bin/system-updater/src/args.rs
deleted file mode 100644
index b6d6f98..0000000
--- a/src/sys/pkg/bin/system-updater/src/args.rs
+++ /dev/null
@@ -1,187 +0,0 @@
-// 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 {
-    argh::FromArgs,
-    fidl_fuchsia_update_installer_ext::Initiator as ExtInitiator,
-    fuchsia_url::pkg_url::PkgUrl,
-    std::time::{Duration, SystemTime},
-};
-
-#[derive(Debug, Eq, FromArgs, PartialEq)]
-/// Arguments for the system updater.
-pub struct Args {
-    /// what started this update: manual or automatic
-    #[argh(option, default = "Initiator::Automatic")]
-    pub initiator: Initiator,
-
-    /// current OS version
-    #[argh(option, default = "String::from(\"\")")]
-    pub source: String,
-
-    /// target OS version
-    #[argh(option, default = "String::from(\"\")")]
-    pub target: String,
-
-    /// update package url
-    #[argh(option, default = "\"fuchsia-pkg://fuchsia.com/update\".parse().unwrap()")]
-    pub update: PkgUrl,
-
-    /// if true, reboot the system after successful OTA
-    #[argh(option, default = "true")]
-    pub reboot: bool,
-
-    /// if true, don't write the recovery image
-    #[argh(option, default = "false")]
-    pub skip_recovery: bool,
-
-    /// start time of update attempt, as unix nanosecond timestamp
-    #[argh(option, from_str_fn(parse_wall_time))]
-    pub start: Option<SystemTime>,
-
-    /// if true, the system updater will install a single update and exit
-    #[argh(option, default = "false")]
-    pub oneshot: bool,
-}
-
-fn parse_wall_time(value: &str) -> Result<SystemTime, String> {
-    let since_unix_epoch =
-        Duration::from_nanos(value.parse().map_err(|e: std::num::ParseIntError| e.to_string())?);
-
-    Ok(SystemTime::UNIX_EPOCH + since_unix_epoch)
-}
-
-#[derive(Debug, Clone, Copy, Eq, PartialEq)]
-pub enum Initiator {
-    Automatic,
-    Manual,
-}
-
-impl From<Initiator> for ExtInitiator {
-    fn from(args_initiator: Initiator) -> Self {
-        match args_initiator {
-            Initiator::Manual => ExtInitiator::User,
-            Initiator::Automatic => ExtInitiator::Service,
-        }
-    }
-}
-
-impl From<ExtInitiator> for Initiator {
-    fn from(ext_initiator: ExtInitiator) -> Self {
-        match ext_initiator {
-            ExtInitiator::User => Initiator::Manual,
-            ExtInitiator::Service => Initiator::Automatic,
-        }
-    }
-}
-
-impl std::str::FromStr for Initiator {
-    type Err = anyhow::Error;
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match s {
-            "automatic" => Ok(Initiator::Automatic),
-            "manual" => Ok(Initiator::Manual),
-            not_supported => Err(anyhow::anyhow!("initiator not supported: {:?}", not_supported)),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use {super::*, matches::assert_matches};
-
-    #[test]
-    fn test_initiator() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &["--initiator", "manual"]),
-            Ok(Args { initiator: Initiator::Manual, .. })
-        );
-    }
-
-    #[test]
-    fn test_unknown_initiator() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &["--initiator", "P Sherman"]),
-            Err(_)
-        );
-    }
-
-    #[test]
-    fn test_start() {
-        use chrono::prelude::*;
-
-        const NANOS_IN_1970: u64 = 365 * 24 * 60 * 60 * 1_000_000_000;
-        let nanos_in_1970 = NANOS_IN_1970.to_string();
-
-        let args = Args::from_args(&["system-updater"], &["--start", &nanos_in_1970]).unwrap();
-        assert_eq!(args.start, Some(Utc.ymd(1971, 1, 1).and_hms(0, 0, 0).into()));
-    }
-
-    #[test]
-    fn test_source() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &["--source", "Wallaby Way"]),
-            Ok(Args {
-                source: source_version,
-                ..
-            }) if source_version=="Wallaby Way"
-        );
-    }
-
-    #[test]
-    fn test_target() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &["--target", "Sydney"]),
-            Ok(Args {
-                target: target_version,
-                ..
-            }) if target_version=="Sydney"
-        );
-    }
-
-    #[test]
-    fn test_update() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &["--update", "fuchsia-pkg://fuchsia.com/foo"]),
-            Ok(Args {
-                update: update_pkg_url,
-                ..
-            }) if update_pkg_url.to_string() == "fuchsia-pkg://fuchsia.com/foo"
-        );
-    }
-
-    #[test]
-    fn test_reboot() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &["--reboot", "false"]),
-            Ok(Args { reboot: false, .. })
-        );
-    }
-
-    #[test]
-    fn test_skip_recovery() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &["--skip-recovery", "true"]),
-            Ok(Args { skip_recovery: true, .. })
-        );
-    }
-
-    #[test]
-    fn test_defaults() {
-        assert_matches!(
-            Args::from_args(&["system-updater"], &[]),
-            Ok(Args {
-                initiator: Initiator::Automatic,
-                source: source_version,
-                target: target_version,
-                update: update_pkg_url,
-                reboot: true,
-                skip_recovery: false,
-                ..
-            }) if source_version == "" &&
-                  target_version == "" &&
-                  update_pkg_url.to_string() == "fuchsia-pkg://fuchsia.com/update"
-        );
-    }
-}
diff --git a/src/sys/pkg/bin/system-updater/src/install_manager.rs b/src/sys/pkg/bin/system-updater/src/install_manager.rs
index bfeeb1b..d6ac1ea 100644
--- a/src/sys/pkg/bin/system-updater/src/install_manager.rs
+++ b/src/sys/pkg/bin/system-updater/src/install_manager.rs
@@ -81,8 +81,7 @@
         // Now we can actually start the task that manages the update attempt.
         let update_url = &config.update_url.clone();
         let should_write_recovery = config.should_write_recovery;
-        let (attempt_id, attempt_stream) =
-            updater.update(config, env, Some(reboot_controller)).await;
+        let (attempt_id, attempt_stream) = updater.update(config, env, reboot_controller).await;
         futures::pin_mut!(attempt_stream);
 
         // Set up inspect nodes.
@@ -392,7 +391,7 @@
             &mut self,
             _config: Config,
             _env: Environment,
-            _reboot_controller: Option<RebootController>,
+            _reboot_controller: RebootController,
         ) -> (String, Self::UpdateStream) {
             self.0.next().await.unwrap()
         }
diff --git a/src/sys/pkg/bin/system-updater/src/main.rs b/src/sys/pkg/bin/system-updater/src/main.rs
index 77b17fe..8860beb 100644
--- a/src/sys/pkg/bin/system-updater/src/main.rs
+++ b/src/sys/pkg/bin/system-updater/src/main.rs
@@ -4,24 +4,18 @@
 
 use {
     crate::{
-        args::Args,
         fidl::{FidlServer, UpdateStateNotifier},
         install_manager::start_install_manager,
-        update::{
-            EnvironmentConnector, NamespaceEnvironmentConnector, RealUpdater, UpdateHistory,
-            Updater,
-        },
+        update::{NamespaceEnvironmentConnector, RealUpdater, UpdateHistory},
     },
     anyhow::anyhow,
     fuchsia_async as fasync,
     fuchsia_component::server::ServiceFs,
     fuchsia_syslog::{fx_log_err, fx_log_info, fx_log_warn},
-    futures::prelude::*,
     parking_lot::Mutex,
     std::sync::Arc,
 };
 
-mod args;
 mod fidl;
 mod install_manager;
 pub(crate) mod update;
@@ -31,62 +25,11 @@
     fuchsia_syslog::init_with_tags(&["system-updater"]).expect("can't init logger");
     fx_log_info!("starting system updater");
 
-    let args: Args = argh::from_env();
     let inspector = fuchsia_inspect::Inspector::new();
     let history_node = inspector.root().create_child("history");
 
     let history = Arc::new(Mutex::new(UpdateHistory::load(history_node).await));
 
-    if args.oneshot {
-        // We don't need inspect in oneshot mode, and it won't be served anyway.
-        drop(inspector);
-
-        oneshot_update(args, Arc::clone(&history)).await;
-    } else {
-        serve_fidl(history, inspector).await;
-    }
-}
-
-/// In the oneshot code path, we do NOT serve FIDL.
-/// Instead, we perform a single update based on the provided CLI args, then exit.
-async fn oneshot_update(args: Args, history: Arc<Mutex<UpdateHistory>>) {
-    let config = update::Config::from_args(args);
-
-    let env = match NamespaceEnvironmentConnector::connect() {
-        Ok(env) => env,
-        Err(e) => {
-            fx_log_err!("Error connecting to services: {:#}", anyhow!(e));
-            std::process::exit(1);
-        }
-    };
-
-    let mut done = false;
-    let mut failed = false;
-    let (_, attempt) = RealUpdater::new(history).update(config, env, None).await;
-    futures::pin_mut!(attempt);
-    while let Some(state) = attempt.next().await {
-        assert!(!done, "update stream continued after a terminal state");
-
-        if state.is_terminal() {
-            done = true;
-        }
-        if state.is_failure() {
-            failed = true;
-            // Intentionally don't short circuit on failure.  Dropping attempt before the stream
-            // terminates will drop any pending cleanup work the update attempt hasn't had a chance
-            // to do yet.
-        }
-    }
-    assert!(done, "update stream did not include a terminal state");
-
-    if failed {
-        std::process::exit(1);
-    }
-}
-
-/// In the non-oneshot path, we serve FIDL, ignore any other provided CLI args, and do NOT
-/// perform an update on start. Instead, we wait for a FIDL request to start an update.
-async fn serve_fidl(history: Arc<Mutex<UpdateHistory>>, inspector: fuchsia_inspect::Inspector) {
     let mut fs = ServiceFs::new_local();
     if let Err(e) = fs.take_and_serve_directory_handle() {
         fx_log_err!("error encountered serving directory handle: {:#}", anyhow!(e));
diff --git a/src/sys/pkg/bin/system-updater/src/update.rs b/src/sys/pkg/bin/system-updater/src/update.rs
index 6bfe7e8..ba917bb 100644
--- a/src/sys/pkg/bin/system-updater/src/update.rs
+++ b/src/sys/pkg/bin/system-updater/src/update.rs
@@ -68,7 +68,7 @@
         &mut self,
         config: Config,
         env: Environment,
-        reboot_controller: Option<RebootController>,
+        reboot_controller: RebootController,
     ) -> (String, Self::UpdateStream);
 }
 
@@ -90,7 +90,7 @@
         &mut self,
         config: Config,
         env: Environment,
-        reboot_controller: Option<RebootController>,
+        reboot_controller: RebootController,
     ) -> (String, Self::UpdateStream) {
         let (attempt_id, attempt) =
             update(config, env, Arc::clone(&self.history), reboot_controller).await;
@@ -111,7 +111,7 @@
     config: Config,
     env: Environment,
     history: Arc<Mutex<UpdateHistory>>,
-    reboot_controller: Option<RebootController>,
+    reboot_controller: RebootController,
 ) -> (String, impl FusedStream<Item = State>) {
     let attempt_fut = history.lock().start_update_attempt(
         Options {
@@ -143,7 +143,7 @@
         let cobalt_forwarder_task = Task::spawn(cobalt_forwarder_task);
 
         fx_log_info!("starting system update with config: {:?}", config);
-        cobalt.log_ota_start(&config.target_version, config.initiator, config.start_time);
+        cobalt.log_ota_start("", config.initiator, config.start_time);
 
         let mut target_version = history::Version::default();
 
@@ -152,15 +152,16 @@
             .await;
 
         let status_code = metrics::result_to_status_code(attempt_res.as_ref().map(|_| ()));
+        let target_build_version = target_version.build_version.to_string();
         cobalt.log_ota_result_attempt(
-            &config.target_version,
+            &target_build_version,
             config.initiator,
             history.lock().attempts_for(&source_version, &target_version) + 1,
             phase,
             status_code,
         );
         cobalt.log_ota_result_duration(
-            &config.target_version,
+            &target_build_version,
             config.initiator,
             phase,
             status_code,
@@ -184,14 +185,14 @@
         };
 
         // Figure out if we should reboot.
-        match (mode, reboot_controller, config.should_reboot) {
+        match mode {
             // First priority: Always reboot on ForceRecovery success, even if the caller
             // asked to defer the reboot.
-            (UpdateMode::ForceRecovery, _, _) => {
+            UpdateMode::ForceRecovery => {
                 fx_log_info!("system update in ForceRecovery mode complete, rebooting...");
             }
             // Second priority: Use the attached reboot controller.
-            (UpdateMode::Normal, Some(reboot_controller), _) => {
+            UpdateMode::Normal => {
                 fx_log_info!("system update complete, waiting for initiator to signal reboot.");
                 match reboot_controller.wait_to_reboot().await {
                     CommitAction::Reboot => {
@@ -204,15 +205,6 @@
                     }
                 }
             }
-            // Last priority: Reboot depending on the config.
-            (UpdateMode::Normal, None, true) => {
-                fx_log_info!("system update complete, rebooting...");
-            }
-            (UpdateMode::Normal, None, false) => {
-                fx_log_info!("system update complete, reboot to new version deferred to caller.");
-                state.enter_defer_reboot(&mut co).await;
-                return target_version;
-            }
         }
 
         state.enter_reboot(&mut co).await;
diff --git a/src/sys/pkg/bin/system-updater/src/update/config.rs b/src/sys/pkg/bin/system-updater/src/update/config.rs
index 69c5faa..cf1d282 100644
--- a/src/sys/pkg/bin/system-updater/src/update/config.rs
+++ b/src/sys/pkg/bin/system-updater/src/update/config.rs
@@ -4,8 +4,7 @@
 
 use {
     super::metrics,
-    crate::args,
-    fidl_fuchsia_update_installer_ext::Options,
+    fidl_fuchsia_update_installer_ext::{Initiator as ExtInitiator, Options},
     fuchsia_url::pkg_url::PkgUrl,
     std::time::{Instant, SystemTime},
 };
@@ -13,11 +12,8 @@
 /// Configuration for an update attempt.
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub struct Config {
-    pub(super) initiator: args::Initiator,
-    pub(super) source_version: String,
-    pub(super) target_version: String,
+    pub(super) initiator: Initiator,
     pub update_url: PkgUrl,
-    pub(super) should_reboot: bool,
     pub should_write_recovery: bool,
     pub(super) start_time: SystemTime,
     pub(super) start_time_mono: Instant,
@@ -25,27 +21,6 @@
 }
 
 impl Config {
-    /// Constructs update configuration from command line arguments, filling in details as needed.
-    pub fn from_args(args: args::Args) -> Self {
-        // The OTA attempt started during the update check, so use that time if possible. Fallback
-        // to now if that data wasn't provided.
-        let start_time = args.start.unwrap_or_else(SystemTime::now);
-        let start_time_mono =
-            metrics::system_time_to_monotonic_time(start_time).unwrap_or_else(Instant::now);
-
-        Self {
-            initiator: args.initiator,
-            source_version: args.source,
-            target_version: args.target,
-            update_url: args.update,
-            should_reboot: args.reboot,
-            should_write_recovery: !args.skip_recovery,
-            start_time,
-            start_time_mono,
-            allow_attach_to_existing_attempt: false,
-        }
-    }
-
     /// Constructs update configuration from PkgUrl and Options.
     pub fn from_url_and_options(update_url: PkgUrl, options: Options) -> Self {
         let start_time = SystemTime::now();
@@ -54,10 +29,7 @@
 
         Self {
             initiator: options.initiator.into(),
-            source_version: "".to_string(),
-            target_version: "".to_string(),
             update_url,
-            should_reboot: true,
             should_write_recovery: options.should_write_recovery,
             start_time,
             start_time_mono,
@@ -66,6 +38,30 @@
     }
 }
 
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum Initiator {
+    Automatic,
+    Manual,
+}
+
+impl From<Initiator> for ExtInitiator {
+    fn from(args_initiator: Initiator) -> Self {
+        match args_initiator {
+            Initiator::Manual => ExtInitiator::User,
+            Initiator::Automatic => ExtInitiator::Service,
+        }
+    }
+}
+
+impl From<ExtInitiator> for Initiator {
+    fn from(ext_initiator: ExtInitiator) -> Self {
+        match ext_initiator {
+            ExtInitiator::User => Initiator::Manual,
+            ExtInitiator::Service => Initiator::Automatic,
+        }
+    }
+}
+
 #[cfg(test)]
 pub struct ConfigBuilder<'a> {
     update_url: &'a str,
@@ -103,7 +99,7 @@
             Options {
                 allow_attach_to_existing_attempt: self.allow_attach_to_existing_attempt,
                 should_write_recovery: self.should_write_recovery,
-                initiator: fidl_fuchsia_update_installer_ext::Initiator::User,
+                initiator: ExtInitiator::User,
             },
         ))
     }
@@ -111,55 +107,12 @@
 
 #[cfg(test)]
 mod tests {
-    use {super::*, std::time::Duration};
-
-    #[test]
-    fn config_from_args() {
-        let now = SystemTime::now() - Duration::from_secs(3600);
-
-        let args = args::Args {
-            initiator: args::Initiator::Manual,
-            source: "source-version".to_string(),
-            target: "target-version".to_string(),
-            update: "fuchsia-pkg://example.com/foo".parse().unwrap(),
-            reboot: true,
-            skip_recovery: false,
-            start: Some(now),
-            oneshot: true,
-        };
-
-        let mut config = Config::from_args(args);
-
-        let d = config.start_time_mono.elapsed();
-        assert!(
-            d > Duration::from_secs(3550) && d < Duration::from_secs(3650),
-            "expected start_time_mono to be an hour before now, got delta of: {:?}",
-            d
-        );
-
-        let now_mono = Instant::now();
-
-        config.start_time_mono = now_mono;
-        assert_eq!(
-            config,
-            Config {
-                initiator: args::Initiator::Manual,
-                source_version: "source-version".to_string(),
-                target_version: "target-version".to_string(),
-                update_url: "fuchsia-pkg://example.com/foo".parse().unwrap(),
-                should_reboot: true,
-                should_write_recovery: true,
-                start_time: now,
-                start_time_mono: now_mono,
-                allow_attach_to_existing_attempt: false,
-            }
-        );
-    }
+    use super::*;
 
     #[test]
     fn config_from_url_and_options() {
         let options = Options {
-            initiator: fidl_fuchsia_update_installer_ext::Initiator::User,
+            initiator: ExtInitiator::User,
             allow_attach_to_existing_attempt: true,
             should_write_recovery: true,
         };
@@ -170,7 +123,7 @@
         matches::assert_matches!(
             config,
             Config {
-                initiator: args::Initiator::Manual,
+                initiator: Initiator::Manual,
                 update_url: url,
                 should_write_recovery: true,
                 allow_attach_to_existing_attempt: true,
diff --git a/src/sys/pkg/bin/system-updater/src/update/metrics.rs b/src/sys/pkg/bin/system-updater/src/update/metrics.rs
index 2e4a88f..0709120 100644
--- a/src/sys/pkg/bin/system-updater/src/update/metrics.rs
+++ b/src/sys/pkg/bin/system-updater/src/update/metrics.rs
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 use {
-    crate::args::Initiator,
+    crate::update::config::Initiator,
     cobalt_client::traits::AsEventCode,
     cobalt_sw_delivery_registry as metrics,
     futures::future::Future,
diff --git a/src/sys/pkg/tests/system-updater/BUILD.gn b/src/sys/pkg/tests/system-updater/BUILD.gn
index 321779b..742751e 100644
--- a/src/sys/pkg/tests/system-updater/BUILD.gn
+++ b/src/sys/pkg/tests/system-updater/BUILD.gn
@@ -13,14 +13,12 @@
 
   deps = [
     "//sdk/fidl/fuchsia.cobalt:fuchsia.cobalt-rustc",
-    "//sdk/fidl/fuchsia.hardware.power.statecontrol:fuchsia.hardware.power.statecontrol-rustc",
     "//sdk/fidl/fuchsia.io:fuchsia.io-rustc",
     "//sdk/fidl/fuchsia.logger:fuchsia.logger-rustc",
     "//sdk/fidl/fuchsia.mem:fuchsia.mem-rustc",
     "//sdk/fidl/fuchsia.paver:fuchsia.paver-rustc",
     "//sdk/fidl/fuchsia.pkg:fuchsia.pkg-rustc",
     "//sdk/fidl/fuchsia.space:fuchsia.space-rustc",
-    "//sdk/fidl/fuchsia.sys:fuchsia.sys-rustc",
     "//src/lib/fdio/rust:fdio",
     "//src/lib/fidl/rust/fidl",
     "//src/lib/fuchsia-async",
@@ -38,6 +36,7 @@
     "//src/sys/pkg/testing/mock-reboot",
     "//src/sys/pkg/testing/mock-resolver",
     "//third_party/rust_crates:anyhow",
+    "//third_party/rust_crates:chrono",
     "//third_party/rust_crates:futures",
     "//third_party/rust_crates:hex",
     "//third_party/rust_crates:log",
@@ -59,7 +58,6 @@
     "src/lib.rs",
     "src/mode_force_recovery.rs",
     "src/mode_normal.rs",
-    "src/options.rs",
     "src/progress_reporting.rs",
     "src/reboot_controller.rs",
     "src/update_package.rs",
diff --git a/src/sys/pkg/tests/system-updater/src/board.rs b/src/sys/pkg/tests/system-updater/src/board.rs
index 1b1a731..b1a4c8f 100644
--- a/src/sys/pkg/tests/system-updater/src/board.rs
+++ b/src/sys/pkg/tests/system-updater/src/board.rs
@@ -6,7 +6,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn validates_board() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.set_board_name("x64");
 
@@ -17,20 +17,14 @@
         .add_file("zbi", "fake zbi")
         .add_file("bootloader", "new bootloader");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(resolved_urls(Arc::clone(&env.interactions)), vec![UPDATE_PKG_URL]);
 }
 
 #[fasync::run_singlethreaded(test)]
 async fn rejects_mismatched_board() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.set_board_name("x64");
 
@@ -39,32 +33,23 @@
         .add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
         .add_file("board", "arm")
         .add_file("zbi", "fake zbi")
-        .add_file("bootloader", "new bootloader");
+        .add_file("bootloader", "new bootloader")
+        .add_file("version", "1.2.3.4");
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     // Expect to have failed prior to downloading images.
     assert_eq!(resolved_urls(Arc::clone(&env.interactions)), vec![UPDATE_PKG_URL]);
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
-    let events = OtaMetrics::from_events(logger.cobalt_events.lock().clone());
     assert_eq!(
-        events,
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::Tufupdate as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Error as u32,
-            target: "m3rk13".into(),
+            target: "1.2.3.4".into(),
         }
     );
 }
diff --git a/src/sys/pkg/tests/system-updater/src/channel.rs b/src/sys/pkg/tests/system-updater/src/channel.rs
index 708cb74..95a6f2c 100644
--- a/src/sys/pkg/tests/system-updater/src/channel.rs
+++ b/src/sys/pkg/tests/system-updater/src/channel.rs
@@ -6,7 +6,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn promotes_target_channel_as_current_channel() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.set_target_channel("target-channel");
 
@@ -15,7 +15,7 @@
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake zbi");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs { ..Default::default() }).await.unwrap();
+    env.run_update().await.unwrap();
 
     env.verify_current_channel(Some(b"target-channel"));
 
@@ -23,31 +23,29 @@
 
     env.set_target_channel("target-channel-2");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs { ..Default::default() }).await.unwrap();
+    env.run_update().await.unwrap();
 
     env.verify_current_channel(Some(b"target-channel-2"));
 }
 
 #[fasync::run_singlethreaded(test)]
 async fn succeeds_even_if_target_channel_does_not_exist() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake zbi");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs { ..Default::default() }).await.unwrap();
+    env.run_update().await.unwrap();
 
     env.verify_current_channel(None);
 }
 
 #[fasync::run_singlethreaded(test)]
 async fn does_not_promote_target_channel_on_failure() {
-    let env = TestEnv::builder()
-        .paver_service(|builder| builder.call_hook(|_| Status::INTERNAL))
-        .oneshot(true)
-        .build();
+    let env =
+        TestEnv::builder().paver_service(|builder| builder.call_hook(|_| Status::INTERNAL)).build();
 
     env.set_target_channel("target-channel");
 
@@ -56,7 +54,7 @@
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake zbi");
 
-    let result = env.run_system_updater_oneshot(SystemUpdaterArgs { ..Default::default() }).await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     env.verify_current_channel(None);
diff --git a/src/sys/pkg/tests/system-updater/src/cobalt_metrics.rs b/src/sys/pkg/tests/system-updater/src/cobalt_metrics.rs
index ecdbf66..116cdd9 100644
--- a/src/sys/pkg/tests/system-updater/src/cobalt_metrics.rs
+++ b/src/sys/pkg/tests/system-updater/src/cobalt_metrics.rs
@@ -8,7 +8,7 @@
     status: Status,
     expected_status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode,
 ) {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     let pkg_url = "fuchsia-pkg://fuchsia.com/failure/0?hash=00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100";
 
@@ -19,26 +19,17 @@
 
     env.resolver.url(pkg_url).fail(status);
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::PackageDownload as u32,
             status_code: expected_status_code as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 }
@@ -81,20 +72,14 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn succeeds_even_if_metrics_fail_to_send() {
-    let env = TestEnvBuilder::new().unregister_protocol(Protocol::Cobalt).oneshot(true).build();
+    let env = TestEnvBuilder::new().unregister_protocol(Protocol::Cobalt).build();
 
     env.resolver
         .register_package("update", "upd4t3")
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake zbi");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("run system updater");
+    env.run_update().await.expect("run system updater");
 
     let loggers = env.logger_factory.loggers.lock().clone();
     assert_eq!(loggers.len(), 0);
diff --git a/src/sys/pkg/tests/system-updater/src/fetch_packages.rs b/src/sys/pkg/tests/system-updater/src/fetch_packages.rs
index d858cb1..e2c3077 100644
--- a/src/sys/pkg/tests/system-updater/src/fetch_packages.rs
+++ b/src/sys/pkg/tests/system-updater/src/fetch_packages.rs
@@ -10,16 +10,9 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn fails_on_package_resolver_connect_error() {
-    let env =
-        TestEnvBuilder::new().unregister_protocol(Protocol::PackageResolver).oneshot(true).build();
+    let env = TestEnvBuilder::new().unregister_protocol(Protocol::PackageResolver).build();
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     assert_eq!(
@@ -45,7 +38,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn fails_on_update_package_fetch_error() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -54,26 +47,17 @@
     let system_image_url = SYSTEM_IMAGE_URL;
     env.resolver.mock_resolve_failure(system_image_url, Status::NOT_FOUND);
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::PackageDownload as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Error as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 
@@ -101,7 +85,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn fails_on_content_package_fetch_error() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     let pkg1_url = pinned_pkg_url!("package1/0", "aa");
     let pkg2_url = pinned_pkg_url!("package2/0", "00");
@@ -141,13 +125,7 @@
 
     let ((), ()) = future::join(
         async {
-            let result = env
-                .run_system_updater_oneshot(SystemUpdaterArgs {
-                    initiator: Some(Initiator::User),
-                    target: Some("m3rk13"),
-                    ..Default::default()
-                })
-                .await;
+            let result = env.run_update().await;
             assert!(result.is_err(), "system updater succeeded when it should fail");
         },
         async move {
@@ -170,17 +148,14 @@
     )
     .await;
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::PackageDownload as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Error as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 
@@ -214,7 +189,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn fails_when_package_cache_sync_fails() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
     env.cache_service.set_sync_response(Err(Status::INTERNAL));
     env.resolver
         .register_package("update", "upd4t3")
@@ -223,13 +198,7 @@
         .url(SYSTEM_IMAGE_URL)
         .resolve(&env.resolver.package("system_image/0", SYSTEM_IMAGE_HASH));
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
 
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
diff --git a/src/sys/pkg/tests/system-updater/src/history.rs b/src/sys/pkg/tests/system-updater/src/history.rs
index 426f57a..3864631 100644
--- a/src/sys/pkg/tests/system-updater/src/history.rs
+++ b/src/sys/pkg/tests/system-updater/src/history.rs
@@ -3,49 +3,38 @@
 // found in the LICENSE file.
 
 use {
-    super::{merkle_str, *},
+    super::*,
+    chrono::prelude::*,
     fidl_fuchsia_pkg::PackageUrl,
     fidl_fuchsia_update_installer::{
-        CompleteData, FetchData, InstallationProgress, InstallerMarker, Options, State, UpdateInfo,
-        UpdateResult,
+        CompleteData, FetchData, InstallationProgress, Options, State, UpdateInfo, UpdateResult,
     },
+    matches::assert_matches,
     pretty_assertions::assert_eq,
     serde_json::json,
 };
 
 #[fasync::run_singlethreaded(test)]
 async fn succeeds_without_writable_data() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().mount_data(false).build();
 
     env.resolver
         .register_package("update", "upd4t3")
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake zbi");
 
-    env.run_system_updater_oneshot_args(
-        SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        },
-        SystemUpdaterEnv { mount_data: false, ..Default::default() },
-    )
-    .await
-    .expect("run system updater");
+    env.run_update().await.expect("run system updater");
 
     assert_eq!(env.read_history(), None);
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 
@@ -103,13 +92,31 @@
     value
 }
 
+/// Given a parsed update history value, verify each attempt contains a 'start' time that's later
+/// than 01/01/2020, and return the object with those fields removed.
+fn strip_start_time(mut value: serde_json::Value) -> serde_json::Value {
+    let min_start_time = Utc.ymd(2020, 1, 1).and_hms(0, 0, 0).timestamp_nanos() as u64;
+    value
+        .as_object_mut()
+        .expect("top level is object")
+        .get_mut("content")
+        .expect("top level 'content' key")
+        .as_array_mut()
+        .expect("'content' is array")
+        .iter_mut()
+        .map(|attempt| attempt.as_object_mut().expect("attempt is object"))
+        .for_each(|attempt| {
+            assert_matches!(
+                attempt.remove("start"),
+                Some(serde_json::Value::Number(start)) if start.as_u64().expect("start is u64") > min_start_time
+            );
+        });
+    value
+}
+
 #[fasync::run_singlethreaded(test)]
 async fn writes_history() {
-    let env = TestEnv::builder().oneshot(true).build();
-
-    // source/target CLI params are no longer trusted for update history values.
-    let source = merkle_str!("ab");
-    let target = merkle_str!("ba");
+    let env = TestEnv::builder().build();
 
     assert_eq!(env.read_history(), None);
 
@@ -126,18 +133,19 @@
         "838b5199d12c8ff4ef92bfd9771d2f8781b7b8fd739dd59bcf63f353a1a93f67",
     );
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        source: Some(source),
-        target: Some(target),
-        start: Some(1234567890),
-        ..Default::default()
-    })
+    env.run_update_with_options(
+        UPDATE_PKG_URL,
+        fidl_fuchsia_update_installer_ext::Options {
+            initiator: Initiator::Service,
+            allow_attach_to_existing_attempt: false,
+            should_write_recovery: true,
+        },
+    )
     .await
     .unwrap();
 
     assert_eq!(
-        env.read_history().map(strip_attempt_ids),
+        env.read_history().map(strip_attempt_ids).map(strip_start_time),
         Some(json!({
             "version": "1",
             "content": [{
@@ -157,11 +165,10 @@
                 },
                 "options": {
                     "allow_attach_to_existing_attempt": false,
-                    "initiator": "User",
+                    "initiator": "Service",
                     "should_write_recovery": true,
                 },
-                "url": "fuchsia-pkg://fuchsia.com/update",
-                "start": 1234567890,
+                "url": UPDATE_PKG_URL,
                 "state": {
                     "id": "reboot",
                     "info": {
@@ -179,7 +186,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn replaces_bogus_history() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.write_history(json!({
         "valid": "no",
@@ -190,12 +197,10 @@
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake zbi");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs { start: Some(42), ..Default::default() })
-        .await
-        .unwrap();
+    env.run_update().await.unwrap();
 
     assert_eq!(
-        env.read_history().map(strip_attempt_ids),
+        env.read_history().map(strip_attempt_ids).map(strip_start_time),
         Some(json!({
             "version": "1",
             "content": [{
@@ -214,12 +219,11 @@
                     "build_version": ""
                 },
                 "options": {
-                    "allow_attach_to_existing_attempt": false,
-                    "initiator": "Service",
+                    "allow_attach_to_existing_attempt": true,
+                    "initiator": "User",
                     "should_write_recovery": true,
                 },
-                "url": "fuchsia-pkg://fuchsia.com/update",
-                "start": 42,
+                "url": UPDATE_PKG_URL,
                 "state": {
                     "id": "reboot",
                     "info": {
@@ -237,10 +241,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn increments_attempts_counter_on_retry() {
-    let env = TestEnv::builder().oneshot(true).build();
-
-    let source = merkle_str!("ab");
-    let target = merkle_str!("ba");
+    let env = TestEnv::builder().build();
 
     env.resolver.url("fuchsia-pkg://fuchsia.com/not-found").fail(Status::NOT_FOUND);
     env.resolver
@@ -249,27 +250,21 @@
         .add_file("zbi", "fake zbi");
 
     let _ = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            update: Some("fuchsia-pkg://fuchsia.com/not-found"),
-            source: Some(source),
-            target: Some(target),
-            start: Some(10),
-            ..Default::default()
-        })
+        .run_update_with_options(
+            "fuchsia-pkg://fuchsia.com/not-found",
+            fidl_fuchsia_update_installer_ext::Options {
+                initiator: Initiator::Service,
+                allow_attach_to_existing_attempt: false,
+                should_write_recovery: true,
+            },
+        )
         .await
         .unwrap_err();
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        source: Some(source),
-        target: Some(target),
-        start: Some(20),
-        ..Default::default()
-    })
-    .await
-    .unwrap();
+    env.run_update().await.unwrap();
 
     assert_eq!(
-        env.read_history().map(strip_attempt_ids),
+        env.read_history().map(strip_attempt_ids).map(strip_start_time),
         Some(json!({
             "version": "1",
             "content": [
@@ -289,12 +284,11 @@
                     "build_version": ""
                 },
                 "options": {
-                    "allow_attach_to_existing_attempt": false,
-                    "initiator": "Service",
+                    "allow_attach_to_existing_attempt": true,
+                    "initiator": "User",
                     "should_write_recovery": true,
                 },
                 "url": "fuchsia-pkg://fuchsia.com/update",
-                "start": 20,
                 "state": {
                     "id": "reboot",
                     "info": {
@@ -327,7 +321,6 @@
                     "should_write_recovery": true,
                 },
                 "url": "fuchsia-pkg://fuchsia.com/not-found",
-                "start": 10,
                 "state": {
                     "id": "fail_prepare",
                 },
@@ -416,8 +409,7 @@
         }))
         .build();
 
-    let installer_proxy =
-        env.system_updater.as_ref().unwrap().connect_to_service::<InstallerMarker>().unwrap();
+    let installer_proxy = env.installer_proxy();
 
     assert_eq!(
         installer_proxy.get_last_update_result().await.unwrap(),
@@ -483,8 +475,7 @@
 async fn serves_fidl_without_history_present() {
     let env = TestEnv::new();
 
-    let installer_proxy =
-        env.system_updater.as_ref().unwrap().connect_to_service::<InstallerMarker>().unwrap();
+    let installer_proxy = env.installer_proxy();
 
     assert_eq!(
         installer_proxy.get_last_update_result().await.unwrap(),
diff --git a/src/sys/pkg/tests/system-updater/src/lib.rs b/src/sys/pkg/tests/system-updater/src/lib.rs
index ca848ee..2b8e099 100644
--- a/src/sys/pkg/tests/system-updater/src/lib.rs
+++ b/src/sys/pkg/tests/system-updater/src/lib.rs
@@ -5,12 +5,13 @@
 #![cfg(test)]
 use {
     self::SystemUpdaterInteraction::{BlobfsSync, Gc, PackageResolve, Paver, Reboot},
-    anyhow::Error,
+    anyhow::{anyhow, Context as _, Error},
     cobalt_sw_delivery_registry as metrics, fidl_fuchsia_paver as paver,
     fidl_fuchsia_pkg::PackageResolverRequestStream,
-    fidl_fuchsia_sys::{LauncherProxy, TerminationReason},
     fidl_fuchsia_update_installer::{InstallerMarker, InstallerProxy},
-    fidl_fuchsia_update_installer_ext::Options,
+    fidl_fuchsia_update_installer_ext::{
+        start_update, Initiator, Options, UpdateAttempt, UpdateAttemptError,
+    },
     fuchsia_async as fasync,
     fuchsia_component::{
         client::{App, AppBuilder},
@@ -19,6 +20,7 @@
     fuchsia_pkg_testing::make_packages_json,
     fuchsia_zircon::Status,
     futures::prelude::*,
+    matches::assert_matches,
     mock_paver::{MockPaverService, MockPaverServiceBuilder, PaverEvent},
     mock_reboot::MockRebootService,
     mock_resolver::MockResolverService,
@@ -42,7 +44,6 @@
 mod history;
 mod mode_force_recovery;
 mod mode_normal;
-mod options;
 mod progress_reporting;
 mod reboot_controller;
 mod update_package;
@@ -76,7 +77,7 @@
 struct TestEnvBuilder {
     paver_service_builder: MockPaverServiceBuilder,
     blocked_protocols: HashSet<Protocol>,
-    oneshot: bool,
+    mount_data: bool,
     history: Option<serde_json::Value>,
 }
 
@@ -85,7 +86,7 @@
         TestEnvBuilder {
             paver_service_builder: MockPaverServiceBuilder::new(),
             blocked_protocols: HashSet::new(),
-            oneshot: false,
+            mount_data: true,
             history: None,
         }
     }
@@ -103,8 +104,8 @@
         self
     }
 
-    fn oneshot(mut self, oneshot: bool) -> Self {
-        self.oneshot = oneshot;
+    fn mount_data(mut self, mount_data: bool) -> Self {
+        self.mount_data = mount_data;
         self
     }
 
@@ -114,7 +115,7 @@
     }
 
     fn build(self) -> TestEnv {
-        let Self { paver_service_builder, blocked_protocols, oneshot, history } = self;
+        let Self { paver_service_builder, blocked_protocols, mount_data, history } = self;
 
         // A buffer to store all the interactions the system-updater has with external services.
         let interactions = Arc::new(Mutex::new(vec![]));
@@ -140,16 +141,8 @@
         }
 
         // Set up system-updater to run in --oneshot false code path.
-        let mut system_updater_builder = None;
-        if !oneshot {
-            system_updater_builder = Some(system_updater_app_builder(
-                &data_path,
-                &build_info_path,
-                &misc_path,
-                RawSystemUpdaterArgs(&[]),
-                Default::default(),
-            ));
-        }
+        let system_updater_builder =
+            system_updater_app_builder(&data_path, &build_info_path, &misc_path, mount_data);
 
         // Set up the paver service to push events to our interactions buffer by overriding
         // call_hook and firmware_hook.
@@ -262,11 +255,11 @@
             .expect("nested environment to create successfully");
         fasync::Task::spawn(fs.collect()).detach();
 
-        let system_updater = system_updater_builder
-            .map(|builder| builder.spawn(env.launcher()).expect("system updater to launch"));
+        let system_updater =
+            system_updater_builder.spawn(env.launcher()).expect("system updater to launch");
 
         TestEnv {
-            env,
+            _env: env,
             resolver,
             _paver_service: paver_service,
             _reboot_service: reboot_service,
@@ -284,7 +277,7 @@
 }
 
 struct TestEnv {
-    env: NestedEnvironment,
+    _env: NestedEnvironment,
     resolver: Arc<MockResolverService>,
     _paver_service: Arc<MockPaverService>,
     _reboot_service: Arc<MockRebootService>,
@@ -296,7 +289,7 @@
     build_info_path: PathBuf,
     misc_path: PathBuf,
     interactions: SystemUpdaterInteractions,
-    system_updater: Option<App>,
+    system_updater: App,
 }
 
 impl TestEnv {
@@ -308,10 +301,6 @@
         TestEnvBuilder::new()
     }
 
-    fn launcher(&self) -> &LauncherProxy {
-        self.env.launcher()
-    }
-
     fn take_interactions(&self) -> Vec<SystemUpdaterInteraction> {
         std::mem::replace(&mut *self.interactions.lock(), vec![])
     }
@@ -364,48 +353,55 @@
         .unwrap()
     }
 
-    async fn run_system_updater_oneshot<'a>(
+    async fn start_update(&self) -> Result<UpdateAttempt, UpdateAttemptError> {
+        self.start_update_with_options(&UPDATE_PKG_URL, default_options()).await
+    }
+
+    async fn start_update_with_options(
         &self,
-        args: SystemUpdaterArgs<'a>,
-    ) -> Result<(), fuchsia_component::client::OutputError> {
-        self.run_system_updater_oneshot_args(args, Default::default()).await
+        url: &str,
+        options: Options,
+    ) -> Result<UpdateAttempt, UpdateAttemptError> {
+        start_update(&url.parse().unwrap(), options, &self.installer_proxy(), None).await
     }
 
-    async fn run_system_updater_oneshot_args<'a>(
-        &'a self,
-        args: impl ToSystemUpdaterCliArgs,
-        env: SystemUpdaterEnv,
-    ) -> Result<(), fuchsia_component::client::OutputError> {
-        let launcher = self.launcher();
-
-        let output = system_updater_app_builder(
-            &self.data_path,
-            &self.build_info_path,
-            &self.misc_path,
-            args,
-            env,
-        )
-        .output(launcher)
-        .expect("system updater to launch")
-        .await
-        .expect("no errors while waiting for exit");
-
-        if !output.stdout.is_empty() {
-            eprintln!("TEST: system updater stdout:\n{}", String::from_utf8_lossy(&output.stdout));
-        }
-
-        if !output.stderr.is_empty() {
-            eprintln!("TEST: system updater stderr:\n{}", String::from_utf8_lossy(&output.stderr));
-        }
-
-        assert_eq!(output.exit_status.reason(), TerminationReason::Exited);
-        output.ok()
+    async fn run_update(&self) -> Result<(), Error> {
+        self.run_update_with_options(UPDATE_PKG_URL, default_options()).await
     }
 
-    /// Opens a connection to the installer fidl service, panicking if the system updater was not
-    /// started as a fidl service.
+    async fn run_update_with_options(&self, url: &str, options: Options) -> Result<(), Error> {
+        let mut update_attempt = self.start_update_with_options(url, options).await?;
+
+        while let Some(state) =
+            update_attempt.try_next().await.context("fetching next update state")?
+        {
+            if state.is_success() {
+                // Wait until the stream terminates before returning so that interactions will
+                // include reboot.
+                assert_matches!(update_attempt.try_next().await, Ok(None));
+                return Ok(());
+            } else if state.is_failure() {
+                // Wait until the stream terminates before returning so that any subsequent
+                // attempts won't get already in progress error.
+                assert_matches!(update_attempt.try_next().await, Ok(None));
+                return Err(anyhow!("update attempt failed"));
+            }
+        }
+
+        Err(anyhow!("unexpected end of update attempt"))
+    }
+
+    /// Opens a connection to the installer fidl service.
     fn installer_proxy(&self) -> InstallerProxy {
-        self.system_updater.as_ref().unwrap().connect_to_service::<InstallerMarker>().unwrap()
+        self.system_updater.connect_to_service::<InstallerMarker>().unwrap()
+    }
+
+    async fn get_ota_metrics(&self) -> OtaMetrics {
+        let loggers = self.logger_factory.loggers.lock().clone();
+        assert_eq!(loggers.len(), 1);
+        let logger = loggers.into_iter().next().unwrap();
+        let events = logger.cobalt_events.lock().clone();
+        OtaMetrics::from_events(events)
     }
 }
 
@@ -413,8 +409,7 @@
     data_path: &PathBuf,
     build_info_path: &PathBuf,
     misc_path: &PathBuf,
-    args: impl ToSystemUpdaterCliArgs,
-    env: SystemUpdaterEnv,
+    mount_data: bool,
 ) -> AppBuilder {
     let data_dir = File::open(data_path).expect("open data dir");
     let build_info_dir = File::open(build_info_path).expect("open config dir");
@@ -426,10 +421,9 @@
     .add_dir_to_namespace("/config/build-info".to_string(), build_info_dir)
     .expect("/config/build-info to mount")
     .add_dir_to_namespace("/misc".to_string(), misc_dir)
-    .expect("/misc to mount")
-    .args(args.to_args());
+    .expect("/misc to mount");
 
-    if env.mount_data {
+    if mount_data {
         system_updater = system_updater
             .add_dir_to_namespace("/data".to_string(), data_dir)
             .expect("/data to mount");
@@ -438,99 +432,6 @@
     system_updater
 }
 
-trait ToSystemUpdaterCliArgs {
-    fn to_args(self) -> Vec<String>;
-}
-
-impl ToSystemUpdaterCliArgs for SystemUpdaterArgs<'_> {
-    fn to_args(self) -> Vec<String> {
-        self.build()
-    }
-}
-
-impl ToSystemUpdaterCliArgs for RawSystemUpdaterArgs {
-    fn to_args(self) -> Vec<String> {
-        self.0.into_iter().map(|s| (*s).to_owned()).collect()
-    }
-}
-
-#[derive(Debug)]
-struct SystemUpdaterEnv {
-    mount_data: bool,
-}
-
-impl Default for SystemUpdaterEnv {
-    fn default() -> Self {
-        Self { mount_data: true }
-    }
-}
-
-#[derive(Debug)]
-struct RawSystemUpdaterArgs(&'static [&'static str]);
-
-#[derive(Debug, Default)]
-struct SystemUpdaterArgs<'a> {
-    initiator: Option<Initiator>,
-    source: Option<&'a str>,
-    target: Option<&'a str>,
-    start: Option<i64>,
-    update: Option<&'a str>,
-    reboot: Option<bool>,
-    skip_recovery: Option<bool>,
-}
-
-#[derive(Debug, Clone, Copy)]
-enum Initiator {
-    User,
-    Service,
-}
-
-impl Default for Initiator {
-    fn default() -> Self {
-        Initiator::Service
-    }
-}
-
-impl Initiator {
-    fn to_cli_arg(self) -> String {
-        match self {
-            Initiator::User => "manual".to_string(),
-            Initiator::Service => "automatic".to_string(),
-        }
-    }
-}
-
-impl SystemUpdaterArgs<'_> {
-    fn build(&self) -> Vec<String> {
-        let mut args = vec![];
-
-        if let Some(initiator) = self.initiator {
-            args.extend(vec!["--initiator".to_owned(), initiator.to_cli_arg()]);
-        }
-        if let Some(source) = self.source {
-            args.extend(vec!["--source".to_owned(), source.to_owned()]);
-        }
-        if let Some(target) = self.target {
-            args.extend(vec!["--target".to_owned(), target.to_owned()]);
-        }
-        if let Some(start) = self.start {
-            args.extend(vec!["--start".to_owned(), start.to_string()]);
-        }
-        if let Some(update) = self.update {
-            args.extend(vec!["--update".to_owned(), update.to_owned()]);
-        }
-        if let Some(reboot) = self.reboot {
-            args.extend(vec!["--reboot".to_owned(), reboot.to_string()]);
-        }
-        if let Some(skip_recovery) = self.skip_recovery {
-            args.extend(vec!["--skip-recovery".to_owned(), skip_recovery.to_string()]);
-        }
-        args.extend(vec!["--oneshot".to_owned(), "true".to_owned()]);
-
-        args
-    }
-}
-
 struct MockCacheService {
     sync_response: Mutex<Option<Result<(), Status>>>,
     interactions: SystemUpdaterInteractions,
@@ -707,7 +608,7 @@
 
         // OtaStart only has initiator and hour_of_day, so just check initiator.
         assert_eq!(start.event_codes[0], event_codes[0]);
-        assert_eq!(&start.component, &component);
+        assert_eq!(start.component, Some("".to_string()));
 
         let target = component.expect("a target update merkle");
 
@@ -772,7 +673,7 @@
 
 fn default_options() -> Options {
     Options {
-        initiator: fidl_fuchsia_update_installer_ext::Initiator::User,
+        initiator: Initiator::User,
         allow_attach_to_existing_attempt: true,
         should_write_recovery: true,
     }
diff --git a/src/sys/pkg/tests/system-updater/src/mode_force_recovery.rs b/src/sys/pkg/tests/system-updater/src/mode_force_recovery.rs
index 8b64ffa..444c6a0 100644
--- a/src/sys/pkg/tests/system-updater/src/mode_force_recovery.rs
+++ b/src/sys/pkg/tests/system-updater/src/mode_force_recovery.rs
@@ -20,35 +20,25 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn writes_recovery_and_force_reboots_into_it() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
-    let package_url = SYSTEM_IMAGE_URL;
     env.resolver
         .register_package("update", "upd4t3")
-        .add_file("packages.json", make_packages_json([package_url]))
+        .add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
         .add_file("update-mode", &force_recovery_json())
         .add_file("recovery", "the recovery image")
         .add_file("recovery.vbmeta", "the recovery vbmeta");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("run system updater");
+    env.run_update().await.expect("run system updater");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 
@@ -94,21 +84,14 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn reboots_regardless_of_reboot_arg() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
         .add_file("packages", make_packages_json([]))
         .add_file("update-mode", &force_recovery_json());
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        reboot: Some(false),
-        ..Default::default()
-    })
-    .await
-    .expect("run system updater");
+    env.run_update().await.expect("run system updater");
 
     // Verify we made a reboot call.
     assert_eq!(env.take_interactions().last().unwrap(), &Reboot);
@@ -145,7 +128,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn rejects_zbi() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -154,13 +137,7 @@
         .add_file("bootloader", "new bootloader")
         .add_file("zbi", "fake zbi");
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     assert_eq!(
@@ -186,7 +163,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn rejects_skip_recovery_flag() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -194,12 +171,14 @@
         .add_file("update-mode", &force_recovery_json());
 
     let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            skip_recovery: Some(true),
-            ..Default::default()
-        })
+        .run_update_with_options(
+            UPDATE_PKG_URL,
+            Options {
+                initiator: Initiator::User,
+                allow_attach_to_existing_attempt: true,
+                should_write_recovery: false,
+            },
+        )
         .await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 }
diff --git a/src/sys/pkg/tests/system-updater/src/mode_normal.rs b/src/sys/pkg/tests/system-updater/src/mode_normal.rs
index 9af624c..82eb731 100644
--- a/src/sys/pkg/tests/system-updater/src/mode_normal.rs
+++ b/src/sys/pkg/tests/system-updater/src/mode_normal.rs
@@ -11,35 +11,27 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn updates_the_system() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
         .add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
-        .add_file("zbi", "fake zbi");
+        .add_file("zbi", "fake zbi")
+        .add_file("version", "1.2.3.4");
     env.resolver
         .url(SYSTEM_IMAGE_URL)
         .resolve(&env.resolver.package("system_image/0", SYSTEM_IMAGE_HASH));
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("run system updater");
+    env.run_update().await.expect("run system updater");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "m3rk13".into(),
+            target: "1.2.3.4".into(),
         }
     );
 
@@ -77,7 +69,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn requires_zbi() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -87,13 +79,7 @@
         .url(SYSTEM_IMAGE_URL)
         .resolve(&env.resolver.package("system_image/0", SYSTEM_IMAGE_HASH));
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     assert_eq!(
@@ -120,13 +106,14 @@
 }
 
 #[fasync::run_singlethreaded(test)]
-async fn updates_the_system_no_oneshot() {
+async fn updates_the_system_with_progress() {
     let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
         .add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
-        .add_file("zbi", "fake zbi");
+        .add_file("zbi", "fake zbi")
+        .add_file("version", "5.6.7.8");
     env.resolver
         .url(SYSTEM_IMAGE_URL)
         .resolve(&env.resolver.package("system_image/0", SYSTEM_IMAGE_HASH));
@@ -148,17 +135,14 @@
     );
 
     // Verify metrics reported.
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "".into(),
+            target: "5.6.7.8".into(),
         }
     );
 
diff --git a/src/sys/pkg/tests/system-updater/src/options.rs b/src/sys/pkg/tests/system-updater/src/options.rs
deleted file mode 100644
index 003a9ea..0000000
--- a/src/sys/pkg/tests/system-updater/src/options.rs
+++ /dev/null
@@ -1,176 +0,0 @@
-// 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 {super::*, pretty_assertions::assert_eq};
-
-#[fasync::run_singlethreaded(test)]
-async fn uses_custom_update_package() {
-    let env = TestEnv::builder().oneshot(true).build();
-
-    env.resolver
-        .register_custom_package("another-update/4", "update", "upd4t3r", "fuchsia.com")
-        .add_file("packages.json", make_packages_json([]))
-        .add_file("zbi", "fake zbi");
-
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        update: Some("fuchsia-pkg://fuchsia.com/another-update/4"),
-        ..Default::default()
-    })
-    .await
-    .expect("run system updater");
-
-    assert_eq!(
-        env.take_interactions(),
-        vec![
-            Paver(PaverEvent::QueryCurrentConfiguration),
-            Paver(PaverEvent::ReadAsset {
-                configuration: paver::Configuration::A,
-                asset: paver::Asset::VerifiedBootMetadata
-            }),
-            Paver(PaverEvent::ReadAsset {
-                configuration: paver::Configuration::A,
-                asset: paver::Asset::Kernel
-            }),
-            Paver(PaverEvent::QueryCurrentConfiguration),
-            Paver(PaverEvent::QueryActiveConfiguration),
-            Gc,
-            PackageResolve("fuchsia-pkg://fuchsia.com/another-update/4".to_string()),
-            Gc,
-            BlobfsSync,
-            Paver(PaverEvent::WriteAsset {
-                configuration: paver::Configuration::B,
-                asset: paver::Asset::Kernel,
-                payload: b"fake zbi".to_vec(),
-            }),
-            Paver(PaverEvent::SetConfigurationActive { configuration: paver::Configuration::B }),
-            Paver(PaverEvent::DataSinkFlush),
-            Paver(PaverEvent::BootManagerFlush),
-            Reboot,
-        ]
-    );
-}
-
-#[fasync::run_singlethreaded(test)]
-async fn rejects_invalid_update_package_url() {
-    let env = TestEnv::new();
-
-    let bogus_url = "not-fuchsia-pkg://fuchsia.com/not-a-update";
-
-    env.resolver.mock_resolve_failure(bogus_url, Status::INVALID_ARGS);
-
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            update: Some(bogus_url),
-            ..Default::default()
-        })
-        .await;
-    assert!(result.is_err(), "system updater succeeded when it should fail");
-
-    assert_eq!(env.take_interactions(), vec![]);
-}
-
-#[fasync::run_singlethreaded(test)]
-async fn rejects_unknown_flags() {
-    let env = TestEnv::builder().oneshot(true).build();
-
-    env.resolver
-        .register_package("update", "upd4t3")
-        .add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
-        .add_file("zbi", "fake zbi");
-
-    let result = env
-        .run_system_updater_oneshot_args(
-            RawSystemUpdaterArgs(&["--initiator", "manual", "--target", "m3rk13", "--foo", "bar"]),
-            Default::default(),
-        )
-        .await;
-    assert!(result.is_err(), "system updater succeeded when it should fail");
-    assert_eq!(env.take_interactions(), vec![]);
-}
-
-#[fasync::run_singlethreaded(test)]
-async fn rejects_extra_args() {
-    let env = TestEnv::builder().oneshot(true).build();
-
-    env.resolver
-        .register_package("update", "upd4t3")
-        .add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
-        .add_file("zbi", "fake zbi");
-
-    let result = env
-        .run_system_updater_oneshot_args(
-            RawSystemUpdaterArgs(&["--initiator", "manual", "--target", "m3rk13", "foo"]),
-            Default::default(),
-        )
-        .await;
-    assert!(result.is_err(), "system updater succeeded when it should fail");
-
-    assert_eq!(env.take_interactions(), vec![]);
-}
-
-#[fasync::run_singlethreaded(test)]
-async fn does_not_reboot_if_requested_not_to_reboot() {
-    let env = TestEnv::builder().oneshot(true).build();
-
-    env.resolver
-        .register_package("update", "upd4t3")
-        .add_file("packages.json", make_packages_json([]))
-        .add_file("zbi", "fake zbi");
-
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        reboot: Some(false),
-        ..Default::default()
-    })
-    .await
-    .expect("run system updater");
-
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
-    assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
-        OtaMetrics {
-            initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
-                as u32,
-            phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
-            status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "m3rk13".into(),
-        }
-    );
-
-    assert_eq!(
-        env.take_interactions(),
-        vec![
-            Paver(PaverEvent::QueryCurrentConfiguration),
-            Paver(PaverEvent::ReadAsset {
-                configuration: paver::Configuration::A,
-                asset: paver::Asset::VerifiedBootMetadata
-            }),
-            Paver(PaverEvent::ReadAsset {
-                configuration: paver::Configuration::A,
-                asset: paver::Asset::Kernel
-            }),
-            Paver(PaverEvent::QueryCurrentConfiguration),
-            Paver(PaverEvent::QueryActiveConfiguration),
-            Gc,
-            PackageResolve(UPDATE_PKG_URL.to_string()),
-            Gc,
-            BlobfsSync,
-            Paver(PaverEvent::WriteAsset {
-                configuration: paver::Configuration::B,
-                asset: paver::Asset::Kernel,
-                payload: b"fake zbi".to_vec(),
-            }),
-            Paver(PaverEvent::SetConfigurationActive { configuration: paver::Configuration::B }),
-            Paver(PaverEvent::DataSinkFlush),
-            Paver(PaverEvent::BootManagerFlush),
-        ]
-    );
-}
diff --git a/src/sys/pkg/tests/system-updater/src/progress_reporting.rs b/src/sys/pkg/tests/system-updater/src/progress_reporting.rs
index d4f78e41..94b2a6d 100644
--- a/src/sys/pkg/tests/system-updater/src/progress_reporting.rs
+++ b/src/sys/pkg/tests/system-updater/src/progress_reporting.rs
@@ -8,7 +8,6 @@
         monitor_update, start_update, Initiator, Options, Progress, State, StateId,
         UpdateAttemptError, UpdateInfo, UpdateInfoAndProgress,
     },
-    fuchsia_url::pkg_url::PkgUrl,
     matches::assert_matches,
     pretty_assertions::assert_eq,
 };
@@ -38,11 +37,7 @@
     let handle_pkg3 = env.resolver.url(pkg3_url).block_once();
 
     // Start the system update.
-    let installer_proxy = env.installer_proxy();
-    let mut attempt =
-        start_update(&UPDATE_PKG_URL.parse().unwrap(), default_options(), &installer_proxy, None)
-            .await
-            .unwrap();
+    let mut attempt = env.start_update().await.unwrap();
 
     assert_eq!(attempt.next().await.unwrap().unwrap(), State::Prepare);
 
@@ -121,15 +116,11 @@
     let handle_update_pkg = env.resolver.url(UPDATE_PKG_URL).block_once();
 
     // Start the system update.
-    let installer_proxy = env.installer_proxy();
-    let attempt0 =
-        start_update(&UPDATE_PKG_URL.parse().unwrap(), default_options(), &installer_proxy, None)
-            .await
-            .unwrap();
+    let attempt0 = env.start_update().await.unwrap();
 
     // Attach monitor.
     let attempt1 =
-        monitor_update(Some(attempt0.attempt_id()), &installer_proxy).await.unwrap().unwrap();
+        monitor_update(Some(attempt0.attempt_id()), &env.installer_proxy()).await.unwrap().unwrap();
 
     // Now that we attached both monitors to the current attempt, we can unblock the
     // resolve and resume the update attempt.
@@ -164,21 +155,18 @@
 
     // Start the system update, making 2 start_update requests. The second start_update request
     // is essentially just a monitor_update request in this case.
-    let installer_proxy = env.installer_proxy();
-    let url: PkgUrl = UPDATE_PKG_URL.parse().unwrap();
-    let attempt0 = start_update(&url, default_options(), &installer_proxy, None).await.unwrap();
-    let attempt1 = start_update(
-        &url,
-        Options {
-            initiator: Initiator::User,
-            allow_attach_to_existing_attempt: true,
-            should_write_recovery: true,
-        },
-        &installer_proxy,
-        None,
-    )
-    .await
-    .unwrap();
+    let attempt0 = env.start_update().await.unwrap();
+    let attempt1 = env
+        .start_update_with_options(
+            UPDATE_PKG_URL,
+            Options {
+                initiator: Initiator::User,
+                allow_attach_to_existing_attempt: true,
+                should_write_recovery: true,
+            },
+        )
+        .await
+        .unwrap();
 
     // Now that we attached both monitors to the current attempt, we can unblock the
     // resolve and resume the update attempt.
@@ -212,16 +200,14 @@
 
     // Start the system update.
     let installer_proxy = env.installer_proxy();
-    let compatible_url: PkgUrl = UPDATE_PKG_URL.parse().unwrap();
+    let compatible_url = UPDATE_PKG_URL;
     let compatible_options = Options {
         initiator: Initiator::User,
         allow_attach_to_existing_attempt: true,
         should_write_recovery: true,
     };
     let _attempt =
-        start_update(&compatible_url, compatible_options.clone(), &installer_proxy, None)
-            .await
-            .unwrap();
+        env.start_update_with_options(compatible_url, compatible_options.clone()).await.unwrap();
 
     // Define incompatible options and url.
     let incompatible_options0 = Options {
@@ -234,25 +220,25 @@
         allow_attach_to_existing_attempt: false,
         should_write_recovery: true,
     };
-    let incompatible_url = "fuchsia-pkg://fuchsia.com/different-url".parse().unwrap();
+    let incompatible_url = "fuchsia-pkg://fuchsia.com/different-url";
 
     // Show that start_update requests fail with AlreadyInProgress errors.
     assert_matches!(
-        start_update(&compatible_url, incompatible_options0, &installer_proxy, None)
+        env.start_update_with_options(compatible_url, incompatible_options0)
             .await
             .map(|_| ())
             .unwrap_err(),
         UpdateAttemptError::InstallInProgress
     );
     assert_matches!(
-        start_update(&compatible_url, incompatible_options1, &installer_proxy, None)
+        env.start_update_with_options(compatible_url, incompatible_options1)
             .await
             .map(|_| ())
             .unwrap_err(),
         UpdateAttemptError::InstallInProgress
     );
     assert_matches!(
-        start_update(&incompatible_url, compatible_options.clone(), &installer_proxy, None)
+        env.start_update_with_options(incompatible_url, compatible_options.clone())
             .await
             .map(|_| ())
             .unwrap_err(),
@@ -260,10 +246,15 @@
     );
     let (_, server_end) = fidl::endpoints::create_endpoints().unwrap();
     assert_matches!(
-        start_update(&compatible_url, compatible_options, &installer_proxy, Some(server_end))
-            .await
-            .map(|_| ())
-            .unwrap_err(),
+        start_update(
+            &compatible_url.parse().unwrap(),
+            compatible_options,
+            &installer_proxy,
+            Some(server_end)
+        )
+        .await
+        .map(|_| ())
+        .unwrap_err(),
         UpdateAttemptError::InstallInProgress
     );
 }
diff --git a/src/sys/pkg/tests/system-updater/src/update_package.rs b/src/sys/pkg/tests/system-updater/src/update_package.rs
index e7f8374..63bf260 100644
--- a/src/sys/pkg/tests/system-updater/src/update_package.rs
+++ b/src/sys/pkg/tests/system-updater/src/update_package.rs
@@ -6,7 +6,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn rejects_invalid_package_name() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     // Name the update package something other than "update" and assert that the process fails to
     // validate the update package.
@@ -14,18 +14,12 @@
         .register_custom_package("not_update", "not_update", "upd4t3", "fuchsia.com")
         .add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
         .add_file("zbi", "fake zbi")
-        .add_file("zedboot", "new recovery");
+        .add_file("zedboot", "new recovery")
+        .add_file("version", "build version");
 
     let not_update_package_url = "fuchsia-pkg://fuchsia.com/not_update";
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            update: Some(not_update_package_url),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update_with_options(not_update_package_url, default_options()).await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     // Expect to have failed prior to downloading images.
@@ -50,35 +44,25 @@
         ]
     );
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
-    let events = OtaMetrics::from_events(logger.cobalt_events.lock().clone());
     assert_eq!(
-        events,
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::Tufupdate as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Error as u32,
-            target: "m3rk13".into(),
+            target: "build version".into(),
         }
     );
 }
 
 #[fasync::run_singlethreaded(test)]
 async fn fails_if_package_unavailable() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver.mock_resolve_failure(UPDATE_PKG_URL, Status::NOT_FOUND);
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     assert_eq!(
@@ -100,3 +84,47 @@
         ]
     );
 }
+
+#[fasync::run_singlethreaded(test)]
+async fn uses_custom_update_package() {
+    let env = TestEnv::builder().build();
+
+    env.resolver
+        .register_custom_package("another-update/4", "update", "upd4t3r", "fuchsia.com")
+        .add_file("packages.json", make_packages_json([]))
+        .add_file("zbi", "fake zbi");
+
+    env.run_update_with_options("fuchsia-pkg://fuchsia.com/another-update/4", default_options())
+        .await
+        .expect("run system updater");
+
+    assert_eq!(
+        env.take_interactions(),
+        vec![
+            Paver(PaverEvent::QueryCurrentConfiguration),
+            Paver(PaverEvent::ReadAsset {
+                configuration: paver::Configuration::A,
+                asset: paver::Asset::VerifiedBootMetadata
+            }),
+            Paver(PaverEvent::ReadAsset {
+                configuration: paver::Configuration::A,
+                asset: paver::Asset::Kernel
+            }),
+            Paver(PaverEvent::QueryCurrentConfiguration),
+            Paver(PaverEvent::QueryActiveConfiguration),
+            Gc,
+            PackageResolve("fuchsia-pkg://fuchsia.com/another-update/4".to_string()),
+            Gc,
+            BlobfsSync,
+            Paver(PaverEvent::WriteAsset {
+                configuration: paver::Configuration::B,
+                asset: paver::Asset::Kernel,
+                payload: b"fake zbi".to_vec(),
+            }),
+            Paver(PaverEvent::SetConfigurationActive { configuration: paver::Configuration::B }),
+            Paver(PaverEvent::DataSinkFlush),
+            Paver(PaverEvent::BootManagerFlush),
+            Reboot,
+        ]
+    );
+}
diff --git a/src/sys/pkg/tests/system-updater/src/writes_firmware.rs b/src/sys/pkg/tests/system-updater/src/writes_firmware.rs
index 2fe51d4..4605358 100644
--- a/src/sys/pkg/tests/system-updater/src/writes_firmware.rs
+++ b/src/sys/pkg/tests/system-updater/src/writes_firmware.rs
@@ -6,7 +6,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn writes_bootloader() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -14,13 +14,7 @@
         .add_file("zbi", "fake zbi")
         .add_file("bootloader", "new bootloader");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(
         env.take_interactions(),
@@ -60,7 +54,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn writes_firmware() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -68,13 +62,7 @@
         .add_file("zbi", "fake zbi")
         .add_file("firmware", "fake firmware");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(
         env.take_interactions(),
@@ -114,7 +102,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn writes_multiple_firmware_types() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -123,13 +111,7 @@
         .add_file("firmware_a", "fake firmware A")
         .add_file("firmware_b", "fake firmware B");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     let mut interactions = env.take_interactions();
     // The order of files listed from a directory isn't guaranteed so the
@@ -192,7 +174,6 @@
         .paver_service(|builder| {
             builder.firmware_hook(|_| paver::WriteFirmwareResult::Unsupported(true))
         })
-        .oneshot(true)
         .build();
 
     env.resolver
@@ -202,13 +183,7 @@
         .add_file("firmware", "fake firmware");
 
     // Update should still succeed, we want to skip unsupported firmware types.
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(
         env.take_interactions(),
@@ -253,7 +228,6 @@
             builder
                 .firmware_hook(|_| paver::WriteFirmwareResult::Status(Status::INTERNAL.into_raw()))
         })
-        .oneshot(true)
         .build();
 
     env.resolver
@@ -262,11 +236,5 @@
         .add_file("zbi", "fake zbi")
         .add_file("firmware", "fake firmware");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect_err("update should fail");
+    env.run_update().await.expect_err("update should fail");
 }
diff --git a/src/sys/pkg/tests/system-updater/src/writes_images.rs b/src/sys/pkg/tests/system-updater/src/writes_images.rs
index 68ddb05..322bcaf 100644
--- a/src/sys/pkg/tests/system-updater/src/writes_images.rs
+++ b/src/sys/pkg/tests/system-updater/src/writes_images.rs
@@ -6,20 +6,14 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn fails_on_paver_connect_error() {
-    let env = TestEnv::builder().unregister_protocol(Protocol::Paver).oneshot(true).build();
+    let env = TestEnv::builder().unregister_protocol(Protocol::Paver).build();
 
     env.resolver
         .register_package("update", "upd4t3")
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake_zbi");
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
     // Appmgr will close the paver service channel when it is unable to forward the channel to any
@@ -45,7 +39,6 @@
                 _ => Status::OK,
             })
         })
-        .oneshot(true)
         .build();
 
     env.resolver
@@ -53,26 +46,17 @@
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake_zbi");
 
-    let result = env
-        .run_system_updater_oneshot(SystemUpdaterArgs {
-            initiator: Some(Initiator::User),
-            target: Some("m3rk13"),
-            ..Default::default()
-        })
-        .await;
+    let result = env.run_update().await;
     assert!(result.is_err(), "system updater succeeded when it should fail");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::ImageWrite as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Error as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 
@@ -105,7 +89,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn skip_recovery_does_not_write_recovery_or_vbmeta() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -114,12 +98,10 @@
         .add_file("zedboot", "new recovery")
         .add_file("recovery.vbmeta", "new recovery vbmeta");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        skip_recovery: Some(true),
-        ..Default::default()
-    })
+    env.run_update_with_options(
+        UPDATE_PKG_URL,
+        Options { should_write_recovery: false, ..default_options() },
+    )
     .await
     .expect("success");
 
@@ -158,7 +140,6 @@
 async fn writes_to_both_configs_if_abr_not_supported() {
     let env = TestEnv::builder()
         .paver_service(|builder| builder.boot_manager_close_with_epitaph(Status::NOT_SUPPORTED))
-        .oneshot(true)
         .build();
 
     env.resolver
@@ -166,25 +147,16 @@
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake_zbi");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 
@@ -228,7 +200,6 @@
                 .current_config(current_config)
                 .active_config(active_config)
         })
-        .oneshot(true)
         .build();
 
     env.resolver
@@ -236,25 +207,16 @@
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake_zbi");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );
 
@@ -405,7 +367,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn writes_recovery_called_legacy_zedboot() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -413,13 +375,7 @@
         .add_file("zbi", "fake zbi")
         .add_file("zedboot", "new recovery");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(
         env.take_interactions(),
@@ -460,7 +416,7 @@
 // TODO(fxbug.dev/52356): drop this duplicate test when "zedboot" is no longer allowed/used.
 #[fasync::run_singlethreaded(test)]
 async fn writes_recovery() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -468,13 +424,7 @@
         .add_file("zbi", "fake zbi")
         .add_file("recovery", "new recovery");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(
         env.take_interactions(),
@@ -514,7 +464,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn writes_recovery_vbmeta() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -523,13 +473,7 @@
         .add_file("zedboot", "new recovery")
         .add_file("recovery.vbmeta", "new recovery vbmeta");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(
         env.take_interactions(),
@@ -574,7 +518,7 @@
 
 #[fasync::run_singlethreaded(test)]
 async fn writes_fuchsia_vbmeta() {
-    let env = TestEnv::builder().oneshot(true).build();
+    let env = TestEnv::builder().build();
 
     env.resolver
         .register_package("update", "upd4t3")
@@ -582,13 +526,7 @@
         .add_file("zbi", "fake zbi")
         .add_file("fuchsia.vbmeta", "fake zbi vbmeta");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
     assert_eq!(
         env.take_interactions(),
@@ -645,7 +583,6 @@
                 builder
             }
         })
-        .oneshot(true)
         .build();
 
     env.resolver
@@ -653,25 +590,16 @@
         .add_file("packages.json", make_packages_json([]))
         .add_file("zbi", "fake_zbi");
 
-    env.run_system_updater_oneshot(SystemUpdaterArgs {
-        initiator: Some(Initiator::User),
-        target: Some("m3rk13"),
-        ..Default::default()
-    })
-    .await
-    .expect("success");
+    env.run_update().await.expect("success");
 
-    let loggers = env.logger_factory.loggers.lock().clone();
-    assert_eq!(loggers.len(), 1);
-    let logger = loggers.into_iter().next().unwrap();
     assert_eq!(
-        OtaMetrics::from_events(logger.cobalt_events.lock().clone()),
+        env.get_ota_metrics().await,
         OtaMetrics {
             initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
                 as u32,
             phase: metrics::OtaResultAttemptsMetricDimensionPhase::SuccessPendingReboot as u32,
             status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Success as u32,
-            target: "m3rk13".into(),
+            target: "".into(),
         }
     );