| // 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. |
| |
| #![warn(missing_docs)] |
| |
| //! Crate to provide fidl logging and test setup helpers for conformance tests |
| //! for fuchsia.io. |
| |
| use { |
| fidl::{ |
| endpoints::{create_proxy, ClientEnd, ProtocolMarker, Proxy}, |
| AsHandleRef, |
| }, |
| fidl_fuchsia_io as fio, fidl_fuchsia_io_test as io_test, |
| fuchsia_async::{DurationExt, TimeoutExt}, |
| fuchsia_zircon as zx, |
| futures::StreamExt, |
| }; |
| |
| /// Test harness helper struct. |
| pub mod test_harness; |
| |
| /// Utility functions for getting combinations of flags. |
| pub mod flags; |
| |
| /// A common name for a file to create in a conformance test. |
| pub const TEST_FILE: &str = "testing.txt"; |
| |
| /// A common set of file contents to write into a test file in a conformance test. |
| pub const TEST_FILE_CONTENTS: &[u8] = "abcdef".as_bytes(); |
| |
| /// A default value for NodeAttributes, with zeros set for all fields. |
| pub const EMPTY_NODE_ATTRS: fio::NodeAttributes = fio::NodeAttributes { |
| mode: 0, |
| id: 0, |
| content_size: 0, |
| storage_size: 0, |
| link_count: 0, |
| creation_time: 0, |
| modification_time: 0, |
| }; |
| |
| /// Listens for the `OnOpen` event and returns its [Status]. This takes the event stream out of the |
| /// proxy, which can only be done once. |
| pub async fn get_open_status(node_proxy: &fio::NodeProxy) -> zx::Status { |
| let mut events = node_proxy.take_event_stream(); |
| if let Some(result) = events.next().await { |
| match result.expect("FIDL error") { |
| fio::NodeEvent::OnOpen_ { s, info: _ } => zx::Status::from_raw(s), |
| fio::NodeEvent::OnRepresentation { .. } => zx::Status::OK, |
| } |
| } else { |
| zx::Status::PEER_CLOSED |
| } |
| } |
| |
| /// Asserts that no OnOpen event was sent on an opened proxy. This takes the event stream out of |
| /// the proxy, which can only be done once. |
| pub async fn assert_on_open_not_received(node_proxy: &fio::NodeProxy) { |
| let mut events = node_proxy.take_event_stream(); |
| // Wait at most 200ms for an OnOpen event to appear. |
| let event = |
| events.next().on_timeout(zx::Duration::from_millis(200).after_now(), || Option::None).await; |
| assert!(event.is_none(), "Unexpected OnOpen event received"); |
| } |
| |
| /// Converts a generic `NodeProxy` to either a file or directory proxy. |
| pub fn convert_node_proxy<T: ProtocolMarker>(proxy: fio::NodeProxy) -> T::Proxy { |
| T::Proxy::from_channel(proxy.into_channel().expect("Cannot convert node proxy to channel")) |
| } |
| |
| /// Helper function to open the desired node in the root folder. |
| /// Asserts that open_node_status succeeds. |
| pub async fn open_node<T: ProtocolMarker>( |
| dir: &fio::DirectoryProxy, |
| flags: fio::OpenFlags, |
| path: &str, |
| ) -> T::Proxy { |
| open_node_status::<T>(dir, flags, path) |
| .await |
| .unwrap_or_else(|e| panic!("open_node_status failed for {path} (flags={flags:?}): {e:?}")) |
| } |
| |
| /// Helper function to open the desired node in the root folder. |
| pub async fn open_node_status<T: ProtocolMarker>( |
| dir: &fio::DirectoryProxy, |
| flags: fio::OpenFlags, |
| path: &str, |
| ) -> Result<T::Proxy, zx::Status> { |
| let flags = flags | fio::OpenFlags::DESCRIBE; |
| let (node_proxy, node_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy"); |
| dir.open(flags, fio::ModeType::empty(), path, node_server).expect("Cannot open node"); |
| let status = get_open_status(&node_proxy).await; |
| |
| if status != zx::Status::OK { |
| Err(status) |
| } else { |
| Ok(convert_node_proxy::<T>(node_proxy)) |
| } |
| } |
| |
| /// Returns the specified node flags from the given NodeProxy. |
| pub async fn get_node_flags(node_proxy: &fio::NodeProxy) -> fio::OpenFlags { |
| node_proxy.get_flags().await.expect("Failed to get node flags!").1 |
| } |
| |
| /// Helper function to open a file with the given flags. Only use this if testing something other |
| /// than the open call directly. |
| pub async fn open_file_with_flags( |
| parent_dir: &fio::DirectoryProxy, |
| flags: fio::OpenFlags, |
| path: &str, |
| ) -> fio::FileProxy { |
| open_node::<fio::FileMarker>(&parent_dir, flags | fio::OpenFlags::NOT_DIRECTORY, path).await |
| } |
| |
| /// Helper function to open a sub-directory with the given flags. Only use this if testing |
| /// something other than the open call directly. |
| pub async fn open_dir_with_flags( |
| parent_dir: &fio::DirectoryProxy, |
| flags: fio::OpenFlags, |
| path: &str, |
| ) -> fio::DirectoryProxy { |
| open_node::<fio::DirectoryMarker>(&parent_dir, flags | fio::OpenFlags::DIRECTORY, path).await |
| } |
| |
| /// Helper function to open a sub-directory as readable and writable. Only use this if testing |
| /// something other than the open call directly. |
| pub async fn open_rw_dir(parent_dir: &fio::DirectoryProxy, path: &str) -> fio::DirectoryProxy { |
| open_dir_with_flags( |
| parent_dir, |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE, |
| path, |
| ) |
| .await |
| } |
| |
| /// Helper function to call `get_token` on a directory. Only use this if testing something |
| /// other than the `get_token` call directly. |
| pub async fn get_token(dir: &fio::DirectoryProxy) -> fidl::Handle { |
| let (status, token) = dir.get_token().await.expect("get_token failed"); |
| assert_eq!(zx::Status::from_raw(status), zx::Status::OK); |
| token.expect("handle missing") |
| } |
| |
| /// Helper function to read a file and return its contents. Only use this if testing something other |
| /// than the read call directly. |
| pub async fn read_file(dir: &fio::DirectoryProxy, path: &str) -> Vec<u8> { |
| let file = open_file_with_flags(dir, fio::OpenFlags::RIGHT_READABLE, path).await; |
| file.read(100).await.expect("read failed").map_err(zx::Status::from_raw).expect("read error") |
| } |
| |
| /// Attempts to open the given file, and checks the status is `NOT_FOUND`. |
| pub async fn assert_file_not_found(dir: &fio::DirectoryProxy, path: &str) { |
| let (file_proxy, file_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy"); |
| dir.open( |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DESCRIBE, |
| fio::ModeType::empty(), |
| path, |
| file_server, |
| ) |
| .expect("Cannot open file"); |
| assert_eq!(get_open_status(&file_proxy).await, zx::Status::NOT_FOUND); |
| } |
| |
| /// Returns the .name field from a given DirectoryEntry, otherwise panics. |
| pub fn get_directory_entry_name(dir_entry: &io_test::DirectoryEntry) -> String { |
| use io_test::DirectoryEntry; |
| match dir_entry { |
| DirectoryEntry::Directory(entry) => entry.name.as_ref(), |
| DirectoryEntry::RemoteDirectory(entry) => entry.name.as_ref(), |
| DirectoryEntry::File(entry) => entry.name.as_ref(), |
| DirectoryEntry::VmoFile(entry) => entry.name.as_ref(), |
| DirectoryEntry::ExecutableFile(entry) => entry.name.as_ref(), |
| } |
| .expect("DirectoryEntry name is None!") |
| .clone() |
| } |
| |
| /// Asserts that the given `vmo_rights` align with the `expected_vmo_rights` passed to a |
| /// get_backing_memory call. We check that the returned rights align with and do not exceed those |
| /// in the given flags, that we have at least basic VMO rights, and that the flags align with the |
| /// expected sharing mode. |
| pub fn validate_vmo_rights(vmo: &zx::Vmo, expected_vmo_rights: fio::VmoFlags) { |
| let vmo_rights: zx::Rights = vmo.basic_info().expect("failed to get VMO info").rights; |
| |
| // Ensure that we have at least some basic rights. |
| assert!(vmo_rights.contains(zx::Rights::BASIC)); |
| assert!(vmo_rights.contains(zx::Rights::MAP)); |
| assert!(vmo_rights.contains(zx::Rights::GET_PROPERTY)); |
| |
| // Ensure the returned rights match and do not exceed those we requested in `expected_vmo_rights`. |
| assert!( |
| vmo_rights.contains(zx::Rights::READ) == expected_vmo_rights.contains(fio::VmoFlags::READ) |
| ); |
| assert!( |
| vmo_rights.contains(zx::Rights::WRITE) |
| == expected_vmo_rights.contains(fio::VmoFlags::WRITE) |
| ); |
| assert!( |
| vmo_rights.contains(zx::Rights::EXECUTE) |
| == expected_vmo_rights.contains(fio::VmoFlags::EXECUTE) |
| ); |
| |
| // Make sure we get SET_PROPERTY if we specified a private copy. |
| if expected_vmo_rights.contains(fio::VmoFlags::PRIVATE_CLONE) { |
| assert!(vmo_rights.contains(zx::Rights::SET_PROPERTY)); |
| } |
| } |
| |
| /// Creates a directory with the given DirectoryEntry, opening the file with the given |
| /// file flags, and returning a Buffer object initialized with the given vmo_flags. |
| pub async fn create_file_and_get_backing_memory( |
| dir_entry: io_test::DirectoryEntry, |
| test_harness: &test_harness::TestHarness, |
| file_flags: fio::OpenFlags, |
| vmo_flags: fio::VmoFlags, |
| ) -> Result<(zx::Vmo, (fio::DirectoryProxy, fio::FileProxy)), zx::Status> { |
| let file_path = get_directory_entry_name(&dir_entry); |
| let root = root_directory(vec![dir_entry]); |
| let dir_proxy = test_harness.get_directory(root, file_flags); |
| let file_proxy = open_node_status::<fio::FileMarker>( |
| &dir_proxy, |
| file_flags | fio::OpenFlags::NOT_DIRECTORY, |
| &file_path, |
| ) |
| .await?; |
| let vmo = file_proxy |
| .get_backing_memory(vmo_flags) |
| .await |
| .expect("get_backing_memory failed") |
| .map_err(zx::Status::from_raw)?; |
| Ok((vmo, (dir_proxy, file_proxy))) |
| } |
| |
| /// Constructs a directory from a set of directory entries. |
| pub fn root_directory(entries: Vec<io_test::DirectoryEntry>) -> io_test::Directory { |
| // Convert the simple vector of entries into the convoluted FIDL field type. |
| let entries: Vec<Option<Box<io_test::DirectoryEntry>>> = |
| entries.into_iter().map(|e| Some(Box::new(e))).collect(); |
| io_test::Directory { name: None, entries: Some(entries), ..Default::default() } |
| } |
| |
| /// Makes a subdirectory with a name and a set of entries. |
| pub fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry { |
| let mut dir = root_directory(entries); |
| dir.name = Some(name.to_string()); |
| io_test::DirectoryEntry::Directory(dir) |
| } |
| |
| /// Makes a remote directory with a name, which forwards the requests to the given directory proxy. |
| pub fn remote_directory(name: &str, remote_dir: fio::DirectoryProxy) -> io_test::DirectoryEntry { |
| let remote_client = ClientEnd::<fio::DirectoryMarker>::new( |
| remote_dir.into_channel().unwrap().into_zx_channel(), |
| ); |
| |
| io_test::DirectoryEntry::RemoteDirectory(io_test::RemoteDirectory { |
| name: Some(name.to_string()), |
| remote_client: Some(remote_client), |
| ..Default::default() |
| }) |
| } |
| |
| /// Makes a file to be placed in the test directory. |
| pub fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry { |
| io_test::DirectoryEntry::File(io_test::File { |
| name: Some(name.to_string()), |
| contents: Some(contents), |
| ..Default::default() |
| }) |
| } |
| |
| /// Makes a vmo file to be placed in the test directory. |
| pub fn vmo_file(name: &str, contents: &[u8], capacity: u64) -> io_test::DirectoryEntry { |
| let vmo = zx::Vmo::create(capacity).expect("Cannot create VMO"); |
| let () = vmo.write(contents, 0).expect("Cannot write to VMO"); |
| let () = vmo.set_content_size(&(contents.len() as u64)).expect("Cannot set VMO content size"); |
| io_test::DirectoryEntry::VmoFile(io_test::VmoFile { |
| name: Some(name.to_string()), |
| vmo: Some(vmo), |
| ..Default::default() |
| }) |
| } |
| |
| /// Makes an executable file to be placed in the test directory. |
| pub fn executable_file(name: &str) -> io_test::DirectoryEntry { |
| io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile { |
| name: Some(name.to_string()), |
| ..Default::default() |
| }) |
| } |