[cm][test_runners] Clone UTC clock handle when spawning tests
Bug: 68080
Multiply: fuchsia-pkg://fuchsia.com/fs-tests#meta/attr-tests.cm
Test: test-runner-unit-tests
Change-Id: I08e10d01f1033d66f2c34331fdf4f691665cd6b6
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/475977
Fuchsia-Auto-Submit: Adam Lesinski <adamlesinski@google.com>
Reviewed-by: Ankur Mittal <anmittal@google.com>
Reviewed-by: Shai Barack <shayba@google.com>
Reviewed-by: Jody Sankey <jsankey@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/src/lib/fuchsia-runtime/src/lib.rs b/src/lib/fuchsia-runtime/src/lib.rs
index 1c201aa..75f9afb 100644
--- a/src/lib/fuchsia-runtime/src/lib.rs
+++ b/src/lib/fuchsia-runtime/src/lib.rs
@@ -14,9 +14,10 @@
use {
fuchsia_zircon::{
- sys::{zx_handle_t, ZX_HANDLE_INVALID}, // handle type (primitive, non-owning)
+ sys::{zx_handle_t, zx_status_t, ZX_HANDLE_INVALID}, // handle type (primitive, non-owning)
Clock,
Handle,
+ HandleBased,
Job,
Process,
Rights,
@@ -41,6 +42,10 @@
pub fn zx_vmar_root_self() -> zx_handle_t;
pub fn zx_job_default() -> zx_handle_t;
pub fn zx_utc_reference_get() -> zx_handle_t;
+ pub fn zx_utc_reference_swap(
+ new_handle: zx_handle_t,
+ prev_handle: *mut zx_handle_t,
+ ) -> zx_status_t;
}
/// Handle types as defined by the processargs protocol.
@@ -336,6 +341,19 @@
handle.duplicate(rights)
}
+/// Swaps the current process-global UTC clock with `new_clock`, returning
+/// the old clock on success.
+/// If `new_clock` is a valid handle but does not have the ZX_RIGHT_READ right,
+/// an error is returned and `new_clock` is dropped.
+pub fn swap_utc_clock_handle(new_clock: Clock) -> Result<Clock, Status> {
+ Ok(unsafe {
+ let mut prev_handle = ZX_HANDLE_INVALID;
+ Status::ok(zx_utc_reference_swap(new_clock.into_raw(), &mut prev_handle))?;
+ Handle::from_raw(prev_handle)
+ }
+ .into())
+}
+
/// Reads time from the UTC `Clock` registered with the runtime.
///
/// # Panics
diff --git a/src/sys/test_runners/src/launch.rs b/src/sys/test_runners/src/launch.rs
index 4a4972a..a059d8c 100644
--- a/src/sys/test_runners/src/launch.rs
+++ b/src/sys/test_runners/src/launch.rs
@@ -14,7 +14,7 @@
runner::component::ComponentNamespace,
runtime::{HandleInfo, HandleType},
thiserror::Error,
- zx::{AsHandleRef, HandleBased, Process, Task},
+ zx::{AsHandleRef, HandleBased, Process, Rights, Task},
};
/// Error encountered while launching a component.
@@ -41,6 +41,9 @@
#[error("Error launching process, cannot create socket {:?}", _0)]
CreateSocket(zx::Status),
+ #[error("Error cloning UTC clock: {:?}", _0)]
+ UtcClock(zx::Status),
+
#[error("unexpected error")]
UnExpectedError,
}
@@ -61,7 +64,8 @@
pub name_infos: Option<Vec<fproc::NameInfo>>,
/// Process environment variables.
pub environs: Option<Vec<String>>,
- // Extra handle infos to add. Handles for stdout and stderr are added.
+ /// Extra handle infos to add. Handles for stdout, stderr, and utc_clock are added.
+ /// The UTC clock handle is cloned from the current process.
pub handle_infos: Option<Vec<fproc::HandleInfo>>,
}
@@ -70,7 +74,13 @@
args: LaunchProcessArgs<'_>,
) -> Result<(Process, ScopedJob, LoggerStream), LaunchError> {
let launcher = connect_to_service::<fproc::LauncherMarker>().map_err(LaunchError::Launcher)?;
+ launch_process_impl(args, launcher).await
+}
+async fn launch_process_impl(
+ args: LaunchProcessArgs<'_>,
+ launcher: fproc::LauncherProxy,
+) -> Result<(Process, ScopedJob, LoggerStream), LaunchError> {
const STDOUT: u16 = 1;
const STDERR: u16 = 2;
@@ -89,6 +99,15 @@
id: HandleInfo::new(HandleType::FileDescriptor, STDERR).as_raw(),
});
+ handle_infos.push(fproc::HandleInfo {
+ handle: runtime::duplicate_utc_clock_handle(
+ Rights::DUPLICATE | Rights::READ | Rights::WAIT | Rights::TRANSFER,
+ )
+ .map_err(LaunchError::UtcClock)?
+ .into_handle(),
+ id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
+ });
+
// Load the component
let mut launch_info =
runner::component::configure_launcher(runner::component::LauncherConfigArgs {
@@ -150,7 +169,15 @@
#[cfg(test)]
mod tests {
- use {super::*, fuchsia_runtime::job_default, fuchsia_zircon as zx};
+ use {
+ super::*,
+ fidl::endpoints::{create_proxy_and_stream, ClientEnd, Proxy},
+ fidl_fuchsia_component_runner as fcrunner, fidl_fuchsia_io as fio, fuchsia_async as fasync,
+ fuchsia_runtime::{job_default, process_self, swap_utc_clock_handle},
+ fuchsia_zircon as zx,
+ futures::prelude::*,
+ std::convert::TryInto,
+ };
#[test]
fn scoped_job_works() {
@@ -184,4 +211,83 @@
// make sure we got back same job handle.
assert_eq!(ret_job.raw_handle(), raw_handle);
}
+
+ #[fasync::run_singlethreaded(test)]
+ async fn utc_clock_is_cloned() {
+ let clock =
+ zx::Clock::create(zx::ClockOpts::MONOTONIC, None).expect("failed to create clock");
+ let expected_clock_koid =
+ clock.as_handle_ref().get_koid().expect("failed to get clock koid");
+
+ // We are affecting the process-wide clock here, but since Rust test cases are run in their
+ // own process, this won't interact with other running tests.
+ let _ = swap_utc_clock_handle(clock).expect("failed to swap clocks");
+
+ // We can't fake all the arguments, as there is actual IO happening. Pass in the bare
+ // minimum that a process needs, and use this test's process handle for real values.
+ let pkg = io_util::open_directory_in_namespace(
+ "/pkg",
+ fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE,
+ )
+ .expect("failed to open pkg");
+ let args = LaunchProcessArgs {
+ bin_path: "bin/test_runners_lib_lib_test", // path to this binary
+ environs: None,
+ args: None,
+ job: None,
+ process_name: "foo",
+ name_infos: None,
+ handle_infos: None,
+ ns: vec![fcrunner::ComponentNamespaceEntry {
+ path: Some("/pkg".into()),
+ directory: Some(ClientEnd::new(pkg.into_channel().unwrap().into_zx_channel())),
+ ..fcrunner::ComponentNamespaceEntry::EMPTY
+ }]
+ .try_into()
+ .unwrap(),
+ };
+ let (mock_proxy, mut mock_stream) = create_proxy_and_stream::<fproc::LauncherMarker>()
+ .expect("failed to create mock handles");
+ let mock_fut = async move {
+ let mut all_handles = vec![];
+ while let Some(request) =
+ mock_stream.try_next().await.expect("failed to get next message")
+ {
+ match request {
+ fproc::LauncherRequest::AddHandles { handles, .. } => {
+ all_handles.extend(handles);
+ }
+ fproc::LauncherRequest::Launch { responder, .. } => {
+ responder
+ .send(
+ zx::Status::OK.into_raw(),
+ Some(
+ process_self()
+ .duplicate(Rights::SAME_RIGHTS)
+ .expect("failed to duplicate process handle"),
+ ),
+ )
+ .expect("failed to send reply");
+ }
+ _ => {}
+ }
+ }
+ return all_handles;
+ };
+ let client_fut = async move {
+ let _ = launch_process_impl(args, mock_proxy).await.expect("failed to launch process");
+ };
+
+ let (all_handles, ()) = futures::future::join(mock_fut, client_fut).await;
+ let clock_id = HandleInfo::new(HandleType::ClockUtc, 0).as_raw();
+
+ let utc_clock_handle = all_handles
+ .into_iter()
+ .find_map(
+ |hi: fproc::HandleInfo| if hi.id == clock_id { Some(hi.handle) } else { None },
+ )
+ .expect("UTC clock handle");
+ let clock_koid = utc_clock_handle.get_koid().expect("failed to get koid");
+ assert_eq!(expected_clock_koid, clock_koid);
+ }
}