Skip to content

Commit

Permalink
Test CLI entrypoint with real filesystem
Browse files Browse the repository at this point in the history
  • Loading branch information
orecham committed Sep 10, 2024
1 parent 48adfaf commit f3145b6
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 46 deletions.
37 changes: 37 additions & 0 deletions iceoryx2-bb/testing/src/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,34 @@ macro_rules! assert_that {
}
}
};
($lhs:expr, contains_match |$element:ident| $predicate:expr) => {
{
let mut does_contain = false;
for $element in &$lhs {
if $predicate {
does_contain = true;
break;
}
}
if !does_contain {
assert_that!(message_contains_match $lhs, core::stringify!($predicate));
}
}
};
($lhs:expr, not_contains_match |$element:ident| $predicate:expr) => {
{
let mut does_contain = false;
for $element in &$lhs {
if $predicate {
does_contain = true;
break;
}
}
if does_contain {
assert_that!(message_contains_match $lhs, core::stringify!($predicate));
}
}
};
($lhs:expr, time_at_least $rhs:expr) => {
{
let lval = $lhs.as_secs_f32();
Expand Down Expand Up @@ -237,6 +265,15 @@ macro_rules! assert_that {
assert_that![color_end]
);
};
[message_contains_match $lhs:expr, $predicate:expr] => {
core::panic!(
"assertion failed: {}expr: {} contains no element matching predicate: {}{}",
assert_that![color_start],
core::stringify!($lhs),
$predicate,
assert_that![color_end]
);
};
[message_property $lhs:expr, $lval:expr, $property:expr, $rhs:expr] => {
core::panic!(
"assertion failed: {}expr: {}.{} == {}; value: {} == {}{}",
Expand Down
2 changes: 2 additions & 0 deletions iceoryx2-cli/iox2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ cargo_metadata = "0.18.1"
[dev-dependencies]
iceoryx2-bb-testing = { workspace = true }
tempfile = "3.12.0"
winapi = { version = "0.3", features = ["winnt"] }
pelite = "0.10"
223 changes: 177 additions & 46 deletions iceoryx2-cli/iox2/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,28 @@ pub struct PathsList {
install: Vec<PathBuf>,
}

#[cfg(windows)]
const PATH_SEPARATOR: char = ';';
#[cfg(windows)]
const COMMAND_EXT: &str = "exe";

#[cfg(not(windows))]
const PATH_SEPARATOR: char = ':';
#[cfg(not(windows))]
const COMMAND_EXT: &str = "";

pub trait Environment {
fn install_paths() -> Result<Vec<PathBuf>>;
fn build_paths() -> Result<Vec<PathBuf>>;
}

pub struct PosixEnvironment;
pub struct HostEnvironment;

impl Environment for PosixEnvironment {
impl Environment for HostEnvironment {
fn install_paths() -> Result<Vec<PathBuf>> {
env::var("PATH")
.context("Failed to read PATH environment variable")?
.split(':')
.split(PATH_SEPARATOR)
.map(PathBuf::from)
.filter(|p| p.is_dir())
.map(Ok)
Expand Down Expand Up @@ -87,22 +97,22 @@ where
E: Environment,
{
fn parse_command_name(path: &PathBuf) -> Result<String> {
path.file_name()
let file_stem = path
.file_stem()
.and_then(|os_str| os_str.to_str())
.ok_or_else(|| anyhow!("Invalid file name"))
.and_then(|file_name| {
if path.extension().is_some() {
Err(anyhow!("File has an extension: {}", file_name))
} else {
Ok(file_name)
}
})
.and_then(|file_name| {
file_name
.strip_prefix("iox2-")
.map(String::from)
.ok_or_else(|| anyhow!("Not an iox2 command: {}", file_name))
})
.ok_or_else(|| anyhow!("Invalid file name"))?;

let command_name = file_stem
.strip_prefix("iox2-")
.ok_or_else(|| anyhow!("Not an iox2 command: {}", file_stem))?;

let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");

if extension == COMMAND_EXT {
Ok(command_name.to_string())
} else {
Err(anyhow!("Invalid file extension: {}", extension))
}
}

fn list_commands_in_path(path: &Path, command_type: CommandType) -> Result<Vec<CommandInfo>> {
Expand Down Expand Up @@ -202,7 +212,7 @@ where
}

pub fn paths() -> Result<()> {
paths_impl::<PosixEnvironment>()
paths_impl::<HostEnvironment>()
}

fn list_impl<E>() -> Result<()>
Expand All @@ -225,7 +235,7 @@ where
}

pub fn list() -> Result<()> {
list_impl::<PosixEnvironment>()
list_impl::<HostEnvironment>()
}

fn execute_impl<E>(command_name: &str, args: Option<&[String]>, dev_flag: bool) -> Result<()>
Expand All @@ -251,56 +261,177 @@ where
}

pub fn execute(command_name: &str, args: Option<&[String]>, dev_flag: bool) -> Result<()> {
execute_impl::<PosixEnvironment>(command_name, args, dev_flag)
execute_impl::<HostEnvironment>(command_name, args, dev_flag)
}

#[cfg(test)]
mod tests {

use iceoryx2_bb_testing::assert_that;
use std::env;
use std::ffi::OsString;
use std::fs::File;
use tempfile::TempDir;

use super::*;

fn setup() -> impl FnOnce(OsString) {
// set up mock files to test logic
let original_path = env::var_os("PATH").expect("Failed to get PATH");
const IOX2_PREFIX: &str = "iox2-";
const FOO_COMMAND: &str = "Xt7bK9pL";
const BAR_COMMAND: &str = "m3Qf8RzN";
const BAZ_COMMAND: &str = "P5hJ2wAc";

#[cfg(windows)]
fn create_minimal_exe(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
use std::io::Write;

let mut file = std::fs::File::create(path)?;

// Create a minimal PE file
let mut image = vec![0; 1024];
let mut pe = PeFile::from_bytes_mut(&mut image)?;

// Set up the DOS header
pe.dos_header_mut().e_magic = pelite::image::IMAGE_DOS_SIGNATURE;
pe.dos_header_mut().e_lfanew = 0x40;

// Set up the NT headers
let nt = pe.nt_headers_mut();
nt.Signature = pelite::image::IMAGE_NT_SIGNATURE;
nt.FileHeader.Machine = pelite::image::IMAGE_FILE_MACHINE_AMD64;
nt.FileHeader.NumberOfSections = 1;
nt.FileHeader.SizeOfOptionalHeader = 0xF0;
nt.FileHeader.Characteristics = FileCharacteristics::EXECUTABLE_IMAGE.bits();

// Set up the Optional header
let opt = &mut nt.OptionalHeader;
opt.Magic = pelite::image::IMAGE_NT_OPTIONAL_HDR64_MAGIC;
opt.SizeOfCode = 0x200;
opt.AddressOfEntryPoint = 0x1000;
opt.BaseOfCode = 0x1000;
opt.ImageBase = 0x140000000;
opt.SectionAlignment = 0x1000;
opt.FileAlignment = 0x200;
opt.MajorSubsystemVersion = 6;
opt.SizeOfImage = 0x2000;
opt.SizeOfHeaders = 0x200;
opt.Subsystem = pelite::image::IMAGE_SUBSYSTEM_WINDOWS_CUI;

// Write the PE file
file.write_all(&image)?;

Ok(())
}

let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path().to_path_buf();
macro_rules! create_file {
($path:expr, $file:expr) => {{
let file_path = $path.join($file);
File::create(&file_path).expect("Failed to create file");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&file_path)
.expect("Failed to get metadata")
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&file_path, perms).expect("Failed to set permissions");
}
#[cfg(windows)]
{
if file_path.extension().and_then(|s| s.to_str()) == Some("exe") {
create_minimal_exe(&file_path).expect("Failed to create minimal executable");
}
}
}};
}

let mut paths = env::split_paths(&original_path).collect::<Vec<_>>();
paths.push(temp_path.clone());
let new_path = env::join_paths(paths).expect("Failed to join paths");
env::set_var("PATH", &new_path);
struct TestEnv {
_temp_dir: TempDir,
original_path: String,
}

File::create(temp_path.join("iox2-foo")).expect("Failed to create file");
File::create(temp_path.join("iox2-foo.d")).expect("Failed to create file");
File::create(temp_path.join("iox2-foo.exe")).expect("Failed to create file");
File::create(temp_path.join("iox2-bar")).expect("Failed to create file");
File::create(temp_path.join("iox2-bar.d")).expect("Failed to create file");
File::create(temp_path.join("iox2-bar.exe")).expect("Failed to create file");
impl TestEnv {
fn setup() -> Self {
let original_path = env::var("PATH").expect("Failed to get PATH");

let temp_dir = TempDir::new().expect("Failed to create temp dir");
let temp_path = temp_dir.path().to_path_buf();

let mut paths = env::split_paths(&original_path).collect::<Vec<_>>();
paths.push(temp_path.clone());
let new_path = env::join_paths(paths).expect("Failed to join paths");
env::set_var("PATH", &new_path);

create_file!(temp_path, format!("{}{}", IOX2_PREFIX, FOO_COMMAND));
create_file!(temp_path, format!("{}{}.d", IOX2_PREFIX, FOO_COMMAND));
create_file!(temp_path, format!("{}{}.exe", IOX2_PREFIX, FOO_COMMAND));
create_file!(temp_path, format!("{}{}", IOX2_PREFIX, BAR_COMMAND));
create_file!(temp_path, format!("{}{}.d", IOX2_PREFIX, BAR_COMMAND));
create_file!(temp_path, format!("{}{}.exe", IOX2_PREFIX, BAR_COMMAND));
create_file!(temp_path, BAZ_COMMAND);
create_file!(temp_path, format!("{}.d", BAZ_COMMAND));
create_file!(temp_path, format!("{}.exe", BAZ_COMMAND));

TestEnv {
_temp_dir: temp_dir,
original_path,
}
}
}

// auto clean-up when callers scope ends
move |original_path| {
env::set_var("PATH", original_path);
impl Drop for TestEnv {
fn drop(&mut self) {
env::set_var("PATH", &self.original_path);
}
}

#[test]
fn test_list() {
let _guard = setup();
let _test_env = TestEnv::setup();

let commands = IceoryxCommandFinder::<HostEnvironment>::commands()
.expect("Failed to retrieve commands");

assert_that!(
commands,
contains_match | command | command.name == FOO_COMMAND
);
assert_that!(
commands,
contains_match | command | command.name == BAR_COMMAND
);
assert_that!(
commands,
not_contains_match | command | command.name == BAZ_COMMAND
);
}

let commands = IceoryxCommandFinder::<PosixEnvironment>::commands()
.expect("error retrieving commands");
#[test]
fn test_execute() {
let _test_env = TestEnv::setup();

let commands = IceoryxCommandFinder::<HostEnvironment>::commands()
.expect("Failed to retrieve commands");

let [foo_command, ..] = commands
.iter()
.filter(|cmd| cmd.name == FOO_COMMAND)
.collect::<Vec<_>>()[..]
else {
panic!("Failed to extract CommandInfo of test files");
};

for command in commands {
println!("{}", command.name);
let result = IceoryxCommandExecutor::execute(&foo_command, None);
match result {
Ok(_) => {
println!("Command executed successfully");
}
Err(ref e) => {
println!("Command execution failed with error: {:?}", e);
}
}
assert_that!(result, is_ok);

assert!(true);
let args = vec!["arg1".to_string(), "arg2".to_string()];
let result = IceoryxCommandExecutor::execute(&foo_command, Some(&args));
assert_that!(result, is_ok);
}
}

0 comments on commit f3145b6

Please sign in to comment.