blob: 2097e580d021b6de1c19814ab7a7177ece0798ea [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{format_err, Error},
argh::FromArgs,
fidl_fuchsia_element as felement, fidl_fuchsia_session as fsession, fuchsia_async as fasync,
fuchsia_component::client::connect_to_protocol_at,
};
#[derive(FromArgs, Debug, PartialEq)]
/// Various operations to control sessions.
pub struct Args {
#[argh(subcommand)]
pub command: Command,
}
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand)]
pub enum Command {
Launch(LaunchCommand),
Restart(RestartCommand),
Add(AddCommand),
}
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "launch")]
/// Launch a new session.
pub struct LaunchCommand {
/// the URL for the session to launch.
#[argh(positional)]
pub session_url: String,
}
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "restart")]
/// Restart the current session.
pub struct RestartCommand {}
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "add")]
/// Add an element to the current session.
pub struct AddCommand {
/// the URL for the element to add.
#[argh(positional)]
pub element_url: String,
}
/// Path to protocols exposed by session-manager.
const SESSION_MANAGER_EXPOSED: &str = "/hub-v2/children/core/children/session-manager/exec/expose";
/// Path to protocols exposed by the session.
const SESSION_EXPOSED: &str =
"/hub-v2/children/core/children/session-manager/children/session:session/exec/expose";
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
let Args { command } = argh::from_env();
match command {
Command::Launch(LaunchCommand { session_url }) => {
let launcher =
connect_to_protocol_at::<fsession::LauncherMarker>(SESSION_MANAGER_EXPOSED)?;
match launch_session(&session_url, launcher).await {
Ok(_) => {
println!("Launched session: {:?}", session_url);
Ok(())
}
Err(err) => {
println!("Failed to launch session: {:?}, {:?}", session_url, err);
Err(err)
}
}
}
Command::Restart(RestartCommand {}) => {
let restarter =
connect_to_protocol_at::<fsession::RestarterMarker>(SESSION_MANAGER_EXPOSED)?;
match restart_session(restarter).await {
Ok(_) => {
println!("Restarted the session.");
Ok(())
}
Err(err) => {
println!("Failed to restart session: {:?}", err);
Err(err)
}
}
}
Command::Add(AddCommand { element_url }) => {
let manager_proxy = connect_to_protocol_at::<felement::ManagerMarker>(SESSION_EXPOSED)?;
match add_element(&element_url, manager_proxy).await {
Ok(_) => {
println!("Added element: {:?}", element_url);
Ok(())
}
Err(err) => {
println!("Failed to add element: {:?}, {:?}", element_url, err);
Err(err)
}
}
}
}
}
/// Launches a session.
///
/// # Parameters
/// - `session_url`: The URL of the session to launch.
/// - `launcher`: The launcher proxy to use to launch the session.
///
/// # Errors
/// Returns an error if there is an issue launching the session.
async fn launch_session(session_url: &str, launcher: fsession::LauncherProxy) -> Result<(), Error> {
let result = launcher
.launch(fsession::LaunchConfiguration {
session_url: Some(session_url.to_string()),
..fsession::LaunchConfiguration::EMPTY
})
.await?;
result.map_err(|err: fsession::LaunchError| format_err!("{:?}", err))?;
Ok(())
}
/// Restarts a session.
///
/// # Parameters
/// - `restarter`: The Restarter proxy to use to restart the session.
///
/// # Errors
/// Returns an error if there is either an issue launching the session or
/// there isn't a previous session to be restarted.
async fn restart_session(restarter: fsession::RestarterProxy) -> Result<(), Error> {
let result = restarter.restart().await?;
result.map_err(|err: fsession::RestartError| format_err!("{:?}", err))?;
Ok(())
}
/// Adds an element to the current session.
///
/// # Parameters
/// - `element_url`: The URL of the element to add.
/// - `manager_proxy`: The element manager proxy use to propose the element.
///
/// # Errors
/// Returns an error if there is an issue adding the element.
async fn add_element(
element_url: &str,
manager_proxy: felement::ManagerProxy,
) -> Result<(), Error> {
let spec =
felement::Spec { component_url: Some(element_url.to_string()), ..felement::Spec::EMPTY };
manager_proxy
.propose_element(spec, None)
.await?
.map_err(|err: felement::ProposeElementError| format_err!("{:?}", err))
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints::create_proxy_and_stream,
// fidl_fuchsia.sys2::EventType, TODO(fxbug.dev/47730): re-enable the tests.
fuchsia_async as fasync,
futures::TryStreamExt,
// test_utils_lib::events::{EventSource, Ordering, RecordedEvent},
};
/// Tests that the session_control tool successfully handles a call to LaunchSession with the provided URL.
#[fasync::run_singlethreaded(test)]
async fn test_launch_session() {
let (launcher, mut launcher_server) = create_proxy_and_stream::<fsession::LauncherMarker>()
.expect("Failed to create Launcher FIDL.");
let session_url = "test_session";
let _server_task = fasync::Task::spawn(async move {
let launch_request =
launcher_server.try_next().await.expect("FIDL Error").expect("Stream terminated");
let (configuration, responder) =
launch_request.into_launch().expect("Failed to unwrap launch request");
assert_eq!(configuration.session_url, Some(session_url.to_string()));
let _ = responder.send(&mut Ok(()));
});
assert!(launch_session(&session_url, launcher).await.is_ok());
}
/// Tests that the session_control tool returns an error on a call to LaunchSession when there
/// is no LaunchConfiguration provided.
#[fasync::run_singlethreaded(test)]
async fn test_launch_session_error() {
let (launcher, mut launcher_server) = create_proxy_and_stream::<fsession::LauncherMarker>()
.expect("Failed to create Launcher FIDL.");
let session_url = "test_session";
let _server_task = fasync::Task::spawn(async move {
let launch_request =
launcher_server.try_next().await.expect("FIDL Error").expect("Stream terminated");
let (_, responder) =
launch_request.into_launch().expect("Failed to unwrap launch request");
let _ = responder.send(&mut Err(fsession::LaunchError::NotFound));
});
assert!(launch_session(&session_url, launcher).await.is_err());
}
/// Tests that restart_session makes the appropriate calls to fuchsia.session.Launcher.
#[fasync::run_singlethreaded(test)]
async fn test_restart_session() {
let (restarter, mut restarter_server) =
create_proxy_and_stream::<fsession::RestarterMarker>()
.expect("Failed to create Restarter FIDL.");
let _server_task = fasync::Task::spawn(async move {
let restarter_request =
restarter_server.try_next().await.expect("FIDL Error").expect("Stream terminated");
let responder =
restarter_request.into_restart().expect("Failed to unwrap restarter request");
let _ = responder.send(&mut Ok(()));
});
assert!(restart_session(restarter).await.is_ok());
}
/// Tests that restart_session returns an error when an error is returned from fuchsia.session.Restarter.
#[fasync::run_singlethreaded(test)]
async fn test_restart_session_error() {
let (restarter, mut restarter_server) =
create_proxy_and_stream::<fsession::RestarterMarker>()
.expect("Failed to create Restarter FIDL.");
let _server_task = fasync::Task::spawn(async move {
let restarter_request =
restarter_server.try_next().await.expect("FIDL Error").expect("Stream terminated");
let responder =
restarter_request.into_restart().expect("Failed to unwrap restarter request");
let _ = responder.send(&mut Err(fsession::RestartError::NotFound));
});
assert!(restart_session(restarter).await.is_err());
}
/// Tests that an element is added to the session
#[fasync::run_singlethreaded(test)]
async fn test_add_element() {
let (manager, mut manager_server) = create_proxy_and_stream::<felement::ManagerMarker>()
.expect("Failed to create Manager FIDL.");
let element_url = "test_element";
let _server_task = fasync::Task::spawn(async move {
let propose_request =
manager_server.try_next().await.expect("FIDL Error").expect("Stream terminated");
let felement::ManagerRequest::ProposeElement { spec, responder, .. } = propose_request;
assert_eq!(spec.component_url, Some(element_url.to_string()));
let _ = responder.send(&mut Ok(()));
});
assert!(add_element(element_url, manager).await.is_ok());
}
/// Tests that an element is added to the session
#[fasync::run_singlethreaded(test)]
async fn test_add_element_error() {
let (manager, mut manager_server) = create_proxy_and_stream::<felement::ManagerMarker>()
.expect("Failed to create Manager FIDL.");
let element_url = "test_element";
let _server_task = fasync::Task::spawn(async move {
let propose_request =
manager_server.try_next().await.expect("FIDL Error").expect("Stream terminated");
let felement::ManagerRequest::ProposeElement { responder, .. } = propose_request;
let _ = responder.send(&mut Err(felement::ProposeElementError::InvalidArgs));
});
assert!(add_element(element_url, manager).await.is_err());
}
// TODO(fxbug.dev/47730): re-enable these tests.
// /// Verifies that session control is routed the expected capabilities.
// #[fasync::run_singlethreaded(test)]
// async fn test_capability_routing() {
// let event_source = EventSource::new().unwrap();
// event_source.start_component_tree().await.unwrap();
// let launcher = connect_to_service::<LauncherMarker>().expect("launcher service?");
// let expected_events = vec![
// RecordedEvent {
// event_type: EventType::CapabilityRouted,
// target_moniker: "./session:session:*".to_string(),
// capability_id: Some("elf".to_string()),
// },
// RecordedEvent {
// event_type: EventType::CapabilityRouted,
// target_moniker: "./session:session:*".to_string(),
// capability_id: Some("/svc/fuchsia.session.Launcher".to_string()),
// },
// ];
// let expectation =
// event_source.expect_events(Ordering::Unordered, expected_events).await.unwrap();
// let session_url = "fuchsia-pkg://fuchsia.com/session_control#meta/session_control.cm";
// match launch_session(&session_url, launcher).await {
// Ok(_) => fx_log_info!("Launched session: {:?}", session_url),
// Err(err) => fx_log_info!("Failed to launch session: {:?}, {:?}", session_url, err),
// };
// expectation.await.unwrap();
// }
// /// Verifies that the session is correctly resolved and launched without errors.
// #[fasync::run_singlethreaded(test)]
// async fn test_session_lifecycle() {
// let event_source = EventSource::new().unwrap();
// event_source.start_component_tree().await.unwrap();
// let launcher = connect_to_service::<LauncherMarker>().expect("launcher service?");
// let expected_events = vec![
// RecordedEvent {
// event_type: EventType::Resolved,
// target_moniker: "./session:session:*".to_string(),
// capability_id: None,
// },
// RecordedEvent {
// event_type: EventType::Started,
// target_moniker: "./session:session:*".to_string(),
// capability_id: None,
// },
// ];
// let expectation =
// event_source.expect_events(Ordering::Ordered, expected_events).await.unwrap();
// let session_url = "fuchsia-pkg://fuchsia.com/session_control#meta/session_control.cm";
// match launch_session(&session_url, launcher).await {
// Ok(_) => println!("Launched session: {:?}", session_url),
// Err(err) => println!("Failed to launch session: {:?}, {:?}", session_url, err),
// };
// expectation.await.unwrap();
// }
}