use std::{
process::{Command, Stdio},
time::Duration,
};
#[test]
fn test_help_output() {
let output = Command::new(env!("CARGO_BIN_EXE_probex"))
.arg("--help")
.output()
.expect("failed to execute probex");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("eBPF process tracing tool"));
assert!(stdout.contains("--output"));
assert!(stdout.contains("COMMAND"));
}
#[test]
fn test_requires_command() {
let output = Command::new(env!("CARGO_BIN_EXE_probex"))
.output()
.expect("failed to execute probex");
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("required") || stderr.contains("COMMAND"),
"stderr: {}",
stderr
);
}
#[test]
fn test_version_output() {
let output = Command::new(env!("CARGO_BIN_EXE_probex"))
.arg("--version")
.output()
.expect("failed to execute probex");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("probex"));
}
#[test]
#[ignore = "requires root privileges and eBPF support"]
fn test_trace_sleep() {
use std::fs::File;
use arrow::array::Array;
use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
let temp_file = "/tmp/probex_test_sleep.parquet";
let _ = std::fs::remove_file(temp_file);
let mut child = Command::new(env!("CARGO_BIN_EXE_probex"))
.args(["-o", temp_file, "--", "sleep", "0.1"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn probex");
let timeout = Duration::from_secs(5);
let start = std::time::Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) => {
assert!(status.success(), "probex exited with error");
break;
}
Ok(None) => {
if start.elapsed() > timeout {
child.kill().expect("failed to kill probex");
panic!("probex timed out");
}
std::thread::sleep(Duration::from_millis(100));
}
Err(e) => panic!("error waiting for probex: {}", e),
}
}
let file = File::open(temp_file).expect("failed to open parquet file");
let builder =
ParquetRecordBatchReaderBuilder::try_new(file).expect("failed to create parquet reader");
let reader = builder.build().expect("failed to build reader");
let mut total_rows = 0;
let mut has_process_exit = false;
for batch_result in reader {
let batch = batch_result.expect("failed to read batch");
total_rows += batch.num_rows();
let event_type_col = batch
.column_by_name("event_type")
.expect("missing event_type column");
let event_types = event_type_col
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.expect("event_type should be StringArray");
for i in 0..event_types.len() {
if event_types.value(i) == "process_exit" {
has_process_exit = true;
}
}
}
assert!(total_rows > 0, "expected some output events");
assert!(has_process_exit, "expected at least one process_exit event");
let _ = std::fs::remove_file(temp_file);
}
#[test]
#[ignore = "requires root privileges and eBPF support"]
fn test_output_to_file() {
use std::fs::{self, File};
use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
let temp_file = "/tmp/probex_test_output.parquet";
let _ = fs::remove_file(temp_file);
let status = Command::new(env!("CARGO_BIN_EXE_probex"))
.args(["-o", temp_file, "--", "true"])
.status()
.expect("failed to run probex");
assert!(status.success(), "probex exited with error");
let file = File::open(temp_file).expect("failed to open parquet file");
let builder =
ParquetRecordBatchReaderBuilder::try_new(file).expect("failed to create parquet reader");
let schema = builder.schema();
assert!(
schema.column_with_name("event_type").is_some(),
"missing event_type column"
);
assert!(
schema.column_with_name("ts_ns").is_some(),
"missing ts_ns column"
);
assert!(
schema.column_with_name("pid").is_some(),
"missing pid column"
);
assert!(
schema.column_with_name("cpu").is_some(),
"missing cpu column"
);
let reader = builder.build().expect("failed to build reader");
let mut total_rows = 0;
for batch_result in reader {
let batch = batch_result.expect("failed to read batch");
total_rows += batch.num_rows();
}
assert!(total_rows > 0, "output file has no events");
let _ = fs::remove_file(temp_file);
}
#[test]
#[ignore = "requires root privileges and eBPF support"]
fn test_default_output_file() {
use std::fs;
let default_file = "trace.parquet";
let _ = fs::remove_file(default_file);
let status = Command::new(env!("CARGO_BIN_EXE_probex"))
.args(["--", "true"])
.status()
.expect("failed to run probex");
assert!(status.success(), "probex exited with error");
assert!(
fs::metadata(default_file).is_ok(),
"default output file trace.parquet was not created"
);
let _ = fs::remove_file(default_file);
}