blob: e404c1152fe3804c12fb39e2aec70117df8cac4c [file] [log] [blame]
// 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);
}
}