| // Copyright 2023 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 crate::config; |
| use crate::driver::Driver; |
| use crate::finder::{Answer, Finder}; |
| use crate::net::IpAddr; |
| |
| use std::path::{Path, PathBuf}; |
| |
| use anyhow::{ensure, Context, Result}; |
| use home::home_dir; |
| |
| const TESTBED_NAME: &'static str = "antlion-runner"; |
| |
| /// Driver for running antlion locally on an emulated or hardware testbed with |
| /// optional mDNS discovery when a DHCP server is not available. This is useful |
| /// for testing changes locally in a development environment. |
| pub(crate) struct LocalDriver { |
| target: LocalTarget, |
| output_dir: PathBuf, |
| ssh_binary: PathBuf, |
| ffx_binary: PathBuf, |
| ffx_subtools_search_path: Option<PathBuf>, |
| enable_honeydew: bool, |
| } |
| |
| impl LocalDriver { |
| pub fn new<F>( |
| device: Option<String>, |
| ssh_binary: PathBuf, |
| ssh_key: Option<PathBuf>, |
| ffx_binary: PathBuf, |
| ffx_subtools_search_path: Option<PathBuf>, |
| out_dir: Option<PathBuf>, |
| enable_honeydew: bool, |
| ) -> Result<Self> |
| where |
| F: Finder, |
| { |
| let output_dir = match out_dir { |
| Some(p) => Ok(p), |
| None => std::env::current_dir().context("Failed to get current working directory"), |
| }?; |
| Ok(Self { |
| target: LocalTarget::new::<F>(device, ssh_key)?, |
| output_dir, |
| ssh_binary, |
| ffx_binary, |
| ffx_subtools_search_path, |
| enable_honeydew, |
| }) |
| } |
| } |
| |
| impl Driver for LocalDriver { |
| fn output_path(&self) -> &Path { |
| self.output_dir.as_path() |
| } |
| fn config(&self) -> config::Config { |
| config::Config { |
| testbeds: vec![config::Testbed { |
| name: TESTBED_NAME.to_string(), |
| controllers: config::Controllers { |
| fuchsia_devices: vec![config::Fuchsia { |
| mdns_name: self.target.name.clone(), |
| ip: self.target.ip.clone(), |
| take_bug_report_on_fail: true, |
| ssh_binary_path: self.ssh_binary.clone(), |
| // TODO(http://b/244747218): Remove when ssh_config is refactored away |
| ssh_config: None, |
| ffx_binary_path: self.ffx_binary.clone(), |
| ffx_subtools_search_path: self.ffx_subtools_search_path.clone(), |
| ssh_priv_key: self.target.ssh_key.clone(), |
| pdu_device: None, |
| hard_reboot_on_fail: true, |
| enable_honeydew: self.enable_honeydew, |
| }], |
| ..Default::default() |
| }, |
| test_params: None, |
| }], |
| mobly_params: config::MoblyParams { log_path: self.output_dir.clone() }, |
| } |
| } |
| fn teardown(&self) -> Result<()> { |
| println!( |
| "\nView full antlion logs at {}", |
| self.output_dir.join(TESTBED_NAME).join("latest").display() |
| ); |
| Ok(()) |
| } |
| } |
| |
| /// LocalTargetInfo performs best-effort discovery of target information from |
| /// standard Fuchsia environmental variables. |
| struct LocalTarget { |
| name: String, |
| ip: IpAddr, |
| ssh_key: PathBuf, |
| } |
| |
| impl LocalTarget { |
| fn new<F>(device: Option<String>, ssh_key: Option<PathBuf>) -> Result<Self> |
| where |
| F: Finder, |
| { |
| let device_name = device.or_else(|| match std::env::var("FUCHSIA_DIR") { |
| Ok(dir) => match std::fs::read_to_string(format!("{dir}/out/default.device")) { |
| Ok(name) => Some(name.trim().to_string()), |
| Err(_) => { |
| println!("A default device using \"fx set-device\" has not been set"); |
| println!("Using the first Fuchsia device discovered via mDNS"); |
| None |
| } |
| }, |
| Err(_) => { |
| println!("Neither --device nor FUCHSIA_DIR has been set"); |
| println!("Using the first Fuchsia device discovered via mDNS"); |
| None |
| } |
| }); |
| |
| let Answer { name, ip } = F::find_device(device_name)?; |
| |
| // TODO: Move this validation out to Args |
| let ssh_key = ssh_key |
| .or_else(|| home_dir().map(|p| p.join(".ssh/fuchsia_ed25519").to_path_buf())) |
| .context("Failed to detect the private Fuchsia SSH key")?; |
| |
| ensure!( |
| ssh_key.try_exists().with_context(|| format!( |
| "Failed to check existence of SSH key \"{}\"", |
| ssh_key.display() |
| ))?, |
| "Cannot find SSH key \"{}\"", |
| ssh_key.display() |
| ); |
| |
| Ok(LocalTarget { name, ip, ssh_key }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| use crate::run; |
| use crate::runner::{ExitStatus, Runner}; |
| |
| use indoc::formatdoc; |
| use pretty_assertions::assert_eq; |
| use tempfile::{NamedTempFile, TempDir}; |
| |
| const FUCHSIA_NAME: &'static str = "fuchsia-1234-5678-9abc"; |
| const FUCHSIA_ADDR: &'static str = "fe80::1%2"; |
| const FUCHSIA_IP: &'static str = "fe80::1"; |
| const SCOPE_ID: u32 = 2; |
| |
| struct MockFinder; |
| impl Finder for MockFinder { |
| fn find_device(_: Option<String>) -> Result<Answer> { |
| Ok(Answer { |
| name: FUCHSIA_NAME.to_string(), |
| ip: IpAddr::V6(FUCHSIA_IP.parse().unwrap(), Some(SCOPE_ID)), |
| }) |
| } |
| } |
| |
| #[derive(Default)] |
| struct MockRunner { |
| config: std::cell::Cell<PathBuf>, |
| } |
| impl Runner for MockRunner { |
| fn run(&self, config: PathBuf) -> Result<ExitStatus> { |
| self.config.set(config); |
| Ok(ExitStatus::Ok) |
| } |
| } |
| |
| #[test] |
| fn local_invalid_ssh_key() { |
| let ssh = NamedTempFile::new().unwrap(); |
| let ffx = NamedTempFile::new().unwrap(); |
| let out_dir = TempDir::new().unwrap(); |
| |
| assert!(LocalDriver::new::<MockFinder>( |
| None, |
| ssh.path().to_path_buf(), |
| Some(PathBuf::new()), |
| ffx.path().to_path_buf(), |
| None, |
| Some(out_dir.path().to_path_buf()), |
| false, |
| ) |
| .is_err()); |
| } |
| |
| #[test] |
| fn local() { |
| let ssh = NamedTempFile::new().unwrap(); |
| let ssh_key = NamedTempFile::new().unwrap(); |
| let ffx = NamedTempFile::new().unwrap(); |
| let ffx_subtools = TempDir::new().unwrap(); |
| let out_dir = TempDir::new().unwrap(); |
| |
| let runner = MockRunner::default(); |
| let driver = LocalDriver::new::<MockFinder>( |
| None, |
| ssh.path().to_path_buf(), |
| Some(ssh_key.path().to_path_buf()), |
| ffx.path().to_path_buf(), |
| Some(ffx_subtools.path().to_path_buf()), |
| Some(out_dir.path().to_path_buf()), |
| false, |
| ) |
| .unwrap(); |
| |
| run(runner, driver, None).unwrap(); |
| |
| let got = std::fs::read_to_string(out_dir.path().join("config.yaml")).unwrap(); |
| |
| let ssh_path = ssh.path().display(); |
| let ssh_key_path = ssh_key.path().display(); |
| let ffx_path = ffx.path().display(); |
| let ffx_subtools_path = ffx_subtools.path().display(); |
| let out_path = out_dir.path().display(); |
| let want = formatdoc! {r#" |
| TestBeds: |
| - Name: {TESTBED_NAME} |
| Controllers: |
| FuchsiaDevice: |
| - mdns_name: {FUCHSIA_NAME} |
| ip: {FUCHSIA_ADDR} |
| take_bug_report_on_fail: true |
| ssh_binary_path: {ssh_path} |
| ffx_binary_path: {ffx_path} |
| ffx_subtools_search_path: {ffx_subtools_path} |
| ssh_priv_key: {ssh_key_path} |
| hard_reboot_on_fail: true |
| enable_honeydew: false |
| MoblyParams: |
| LogPath: {out_path} |
| "#}; |
| |
| assert_eq!(got, want); |
| } |
| |
| #[test] |
| fn local_with_test_params() { |
| let ssh = NamedTempFile::new().unwrap(); |
| let ssh_key = NamedTempFile::new().unwrap(); |
| let ffx = NamedTempFile::new().unwrap(); |
| let ffx_subtools = TempDir::new().unwrap(); |
| let out_dir = TempDir::new().unwrap(); |
| |
| let runner = MockRunner::default(); |
| let driver = LocalDriver::new::<MockFinder>( |
| None, |
| ssh.path().to_path_buf(), |
| Some(ssh_key.path().to_path_buf()), |
| ffx.path().to_path_buf(), |
| Some(ffx_subtools.path().to_path_buf()), |
| Some(out_dir.path().to_path_buf()), |
| false, |
| ) |
| .unwrap(); |
| |
| let params_yaml = " |
| sl4f_sanity_test_params: |
| foo: bar |
| "; |
| let params = serde_yaml::from_str(params_yaml).unwrap(); |
| |
| run(runner, driver, Some(params)).unwrap(); |
| |
| let got = std::fs::read_to_string(out_dir.path().join("config.yaml")).unwrap(); |
| |
| let ssh_path = ssh.path().display().to_string(); |
| let ssh_key_path = ssh_key.path().display().to_string(); |
| let ffx_path = ffx.path().display().to_string(); |
| let ffx_subtools_path = ffx_subtools.path().display(); |
| let out_path = out_dir.path().display(); |
| let want = formatdoc! {r#" |
| TestBeds: |
| - Name: {TESTBED_NAME} |
| Controllers: |
| FuchsiaDevice: |
| - mdns_name: {FUCHSIA_NAME} |
| ip: {FUCHSIA_ADDR} |
| take_bug_report_on_fail: true |
| ssh_binary_path: {ssh_path} |
| ffx_binary_path: {ffx_path} |
| ffx_subtools_search_path: {ffx_subtools_path} |
| ssh_priv_key: {ssh_key_path} |
| hard_reboot_on_fail: true |
| enable_honeydew: false |
| TestParams: |
| sl4f_sanity_test_params: |
| foo: bar |
| MoblyParams: |
| LogPath: {out_path} |
| "#}; |
| |
| assert_eq!(got, want); |
| } |
| } |