blob: 64c080448730edd1e84f2f5c8e547b14338b6188 [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::helpers::*;
use anyhow::{anyhow, Context as _, Error};
use fidl::endpoints::{create_proxy, Proxy};
use fidl_fuchsia_component_runner as frunner;
use fidl_fuchsia_data as fdata;
use fidl_fuchsia_io as fio;
use fidl_fuchsia_test as ftest;
use fuchsia_zircon as zx;
use std::collections::HashMap;
struct LtpTestDefinition {
name: String,
command: String,
}
fn parse_test_definition(test_def: &str) -> LtpTestDefinition {
let test_def = test_def.trim();
match test_def.split_once(' ') {
Some((name, command)) => {
LtpTestDefinition { name: name.to_string(), command: command.trim().to_string() }
}
None => LtpTestDefinition { name: test_def.to_string(), command: test_def.to_string() },
}
}
async fn read_tests_list(
start_info: &mut frunner::ComponentStartInfo,
) -> Result<Vec<LtpTestDefinition>, Error> {
let program = start_info.program.as_ref().unwrap();
let tests_list_file = get_str_value_from_dict(program, "tests_list")?;
Ok(read_file_from_component_ns(start_info, &tests_list_file)
.await
.with_context(|| format!("Failed to read {}", tests_list_file))?
.split('\n')
.filter(|s| !s.is_empty())
.map(parse_test_definition)
.collect())
}
pub async fn get_cases_list_for_ltp(
mut start_info: frunner::ComponentStartInfo,
) -> Result<Vec<ftest::Case>, Error> {
let tests_list = read_tests_list(&mut start_info)
.await?
.iter()
.map(|t| t.name.clone())
.collect::<Vec<String>>();
Ok(tests_list
.iter()
.map(|name| ftest::Case { name: Some(name.clone()), ..Default::default() })
.collect())
}
pub async fn run_ltp_cases(
tests: Vec<ftest::Invocation>,
mut start_info: frunner::ComponentStartInfo,
run_listener_proxy: &ftest::RunListenerProxy,
component_runner: &frunner::ComponentRunnerProxy,
) -> Result<(), Error> {
let program = start_info.program.as_ref().unwrap();
let base_path = get_str_value_from_dict(program, "tests_dir")?;
let mut test_commands = HashMap::<String, String>::new();
read_tests_list(&mut start_info).await?.drain(..).for_each(|t| {
test_commands.insert(t.name, t.command);
});
let file_content =
read_file_from_component_ns(&mut start_info, "data/test_results.json").await?;
let expected_test_results: HashMap<String, String> = serde_json::from_str(&file_content)?;
for test in tests {
let test_name = test.name.as_ref().expect("No test name");
let test_command: &String = test_commands
.get(test_name)
.ok_or_else(|| anyhow!("Invalid test name: {}", test_name))?;
let command = vec!["/bin/sh", "-c", test_command];
let (component_controller, std_handles) =
start_command(&mut start_info, component_runner, &base_path, &command)?;
let (case_listener_proxy, case_listener) = create_proxy::<ftest::CaseListenerMarker>()?;
run_listener_proxy.on_test_case_started(&test, std_handles, case_listener)?;
let allow_skipped = expected_test_results[test_name] == "IGNORED";
let result =
read_ltp_test_result(component_controller.take_event_stream(), allow_skipped).await;
case_listener_proxy.finished(&result)?;
}
Ok(())
}
async fn read_file_from_dir(dir: &fio::DirectoryProxy, path: &str) -> Result<String, Error> {
let file_proxy = fuchsia_fs::directory::open_file_no_describe(
&dir,
path,
fuchsia_fs::OpenFlags::RIGHT_READABLE,
)?;
fuchsia_fs::file::read_to_string(&file_proxy).await.map_err(Into::into)
}
async fn read_file_from_component_ns(
start_info: &mut frunner::ComponentStartInfo,
path: &str,
) -> Result<String, Error> {
for entry in start_info.ns.as_mut().ok_or(anyhow!("Component NS is not set"))?.iter_mut() {
if entry.path == Some("/pkg".to_string()) {
let dir = entry.directory.take().ok_or(anyhow!("NS entry directory is not set"))?;
let dir_proxy = dir.into_proxy()?;
let result = read_file_from_dir(&dir_proxy, path).await;
// Return the directory back to the `start_info`.
entry.directory = Some(fidl::endpoints::ClientEnd::new(
dir_proxy.into_channel().unwrap().into_zx_channel(),
));
return result;
}
}
Err(anyhow!("/pkg is not in the namespace"))
}
// Starts a component that runs the specified `binary` with the specified `args`.
fn start_command(
base_start_info: &mut frunner::ComponentStartInfo,
component_runner: &frunner::ComponentRunnerProxy,
cwd: &str,
command: &[&str],
) -> Result<(frunner::ComponentControllerProxy, ftest::StdHandles), Error> {
let mut program_entries = vec![
fdata::DictionaryEntry {
key: "cwd".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str(cwd.to_string()))),
},
fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str(command[0].to_string()))),
},
fdata::DictionaryEntry {
key: "args".to_string(),
value: Some(Box::new(fdata::DictionaryValue::StrVec(
command.iter().skip(1).map(|s| s.to_string()).collect::<Vec<String>>(),
))),
},
];
// Copy "environ" and "uid" from `base_start_info`.
if let Some(fidl_fuchsia_data::Dictionary { entries: Some(entries), .. }) =
base_start_info.program.as_ref()
{
for entry in entries {
match entry.key.as_str() {
"environ" | "uid" => {
program_entries.push(entry.clone());
}
_ => (),
}
}
}
let (numbered_handles, std_handles) = create_numbered_handles();
let start_info = frunner::ComponentStartInfo {
program: Some(fidl_fuchsia_data::Dictionary {
entries: Some(program_entries),
..Default::default()
}),
numbered_handles,
..clone_start_info(base_start_info)?
};
Ok((start_test_component(start_info, component_runner)?, std_handles))
}
async fn read_ltp_test_result(
event_stream: frunner::ComponentControllerEventStream,
allow_skipped: bool,
) -> ftest::Result_ {
// Base value used by the ComponentRunner implementation in Starnix to
// return non-zero error codes (see src/starnix/kernel/execution/component_runner.rs ).
//
// TODO(https://fxbug.dev/42081234): Cleanup this once we have a proper mechanism to
// get Linux exit code from component runner.
const COMPONENT_EXIT_CODE_BASE: i32 = 1024;
// Status code used by LTP tests for skipped tests. See
// https://github.com/linux-test-project/ltp/blob/master/include/tst_res_flags.h .
const LTP_RESULT_TCONF: i32 = COMPONENT_EXIT_CODE_BASE + 32;
match read_component_epitaph(event_stream).await {
zx::Status::OK => {
ftest::Result_ { status: Some(ftest::Status::Passed), ..Default::default() }
}
status if status.into_raw() == LTP_RESULT_TCONF && allow_skipped => {
ftest::Result_ { status: Some(ftest::Status::Skipped), ..Default::default() }
}
_ => ftest::Result_ { status: Some(ftest::Status::Failed), ..Default::default() },
}
}