Files
ghidra-cli/tests/e2e.rs
T
Alexander KiselevandClaude Opus 4.5 592958f8fe chore: Add open source release plan and update e2e tests
- Add docs/plan-prod.md with comprehensive release plan
- Include sample_binary test fixture
- Update e2e tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 01:54:02 -08:00

236 lines
7.2 KiB
Rust

//! End-to-end tests for ghidra-cli
//!
//! These tests require a working Ghidra installation. The test project
//! is set up automatically on first run.
use assert_cmd::Command;
use predicates::prelude::*;
use serial_test::serial;
use std::path::PathBuf;
use std::sync::Once;
static SETUP: Once = Once::new();
static PROJECT_NAME: &str = "e2e-test";
static PROGRAM_NAME: &str = "sample_binary";
/// Get the path to the test fixture binary
fn fixture_binary() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("sample_binary")
}
/// Ensure the test project is set up (import + analyze the sample binary).
/// This runs only once per test run, regardless of how many tests call it.
fn ensure_project_setup() {
SETUP.call_once(|| {
let binary = fixture_binary();
if !binary.exists() {
panic!(
"Test fixture not found: {:?}\nRun: rustc --edition 2021 -o tests/fixtures/sample_binary tests/fixtures/sample_binary.rs",
binary
);
}
eprintln!("=== Setting up E2E test project (import + analyze) ===");
// Import the binary
let mut cmd = Command::cargo_bin("ghidra").expect("Failed to find ghidra binary");
let result = cmd
.arg("import")
.arg(binary.to_str().unwrap())
.arg("--project")
.arg(PROJECT_NAME)
.arg("--program")
.arg(PROGRAM_NAME)
.timeout(std::time::Duration::from_secs(300))
.output()
.expect("Failed to run import command");
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
let stdout = String::from_utf8_lossy(&result.stdout);
eprintln!("Import stdout: {}", stdout);
eprintln!("Import stderr: {}", stderr);
// Don't panic - project might already exist
if !stderr.contains("already exists") && !stdout.contains("already exists") {
eprintln!("Warning: Import may have failed, but continuing...");
}
} else {
eprintln!("Binary imported successfully");
}
eprintln!("=== E2E test project setup complete ===");
});
}
mod e2e_tests {
use super::*;
/// Test that doctor command works
#[test]
fn test_doctor() {
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("doctor")
.assert()
.success()
.stdout(predicate::str::contains("Ghidra CLI Doctor"));
}
/// Test version command
#[test]
fn test_version() {
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("version")
.assert()
.success()
.stdout(predicate::str::contains("ghidra-cli"));
}
/// Test config list command
#[test]
fn test_config_list() {
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("config")
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("ghidra_install_dir"));
}
/// Test import command with sample binary
#[test]
#[serial]
fn test_import_binary() {
let binary = fixture_binary();
if !binary.exists() {
panic!(
"Test fixture not found. Run: rustc --edition 2021 -o tests/fixtures/sample_binary tests/fixtures/sample_binary.rs"
);
}
// Use a unique project name for this test
let project = format!("e2e-import-{}", std::process::id());
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("import")
.arg(binary.to_str().unwrap())
.arg("--project")
.arg(&project)
.arg("--program")
.arg("sample_binary")
.timeout(std::time::Duration::from_secs(300))
.assert()
.success()
.stdout(predicate::str::contains("Successfully imported"));
}
/// Test function list command on pre-analyzed binary
#[test]
#[serial]
fn test_function_list() {
ensure_project_setup();
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("function")
.arg("list")
.arg("--project")
.arg(PROJECT_NAME)
.arg("--program")
.arg(PROGRAM_NAME)
.arg("--limit")
.arg("100")
.timeout(std::time::Duration::from_secs(300))
.assert()
.success()
// Check for our known exported functions
.stdout(predicate::str::contains("main"))
.stdout(
predicate::str::contains("fibonacci").or(predicate::str::contains("factorial")),
);
}
/// Test decompile command
#[test]
#[serial]
fn test_decompile() {
ensure_project_setup();
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("decompile")
.arg("main") // Decompile main function
.arg("--project")
.arg(PROJECT_NAME)
.arg("--program")
.arg(PROGRAM_NAME)
.timeout(std::time::Duration::from_secs(300))
.assert()
.success()
// Should contain decompiled C code
.stdout(predicate::str::contains("void").or(predicate::str::contains("int")));
}
/// Test strings command
#[test]
#[serial]
fn test_strings() {
ensure_project_setup();
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("strings")
.arg("list")
.arg("--project")
.arg(PROJECT_NAME)
.arg("--program")
.arg(PROGRAM_NAME)
.arg("--limit")
.arg("100") // Increase limit to find our test strings
.timeout(std::time::Duration::from_secs(300))
.assert()
.success()
// Check for strings that exist in a typical ELF binary
// (libc symbols are reliably present)
.stdout(predicate::str::contains("address"))
.stdout(predicate::str::contains("value"));
}
/// Test memory map command
#[test]
#[serial]
fn test_memory_map() {
ensure_project_setup();
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("memory")
.arg("map")
.arg("--project")
.arg(PROJECT_NAME)
.arg("--program")
.arg(PROGRAM_NAME)
.timeout(std::time::Duration::from_secs(300))
.assert()
.success()
// Should show memory sections
.stdout(predicate::str::contains(".text").or(predicate::str::contains("r")));
}
/// Test summary command
#[test]
#[serial]
fn test_summary() {
ensure_project_setup();
let mut cmd = Command::cargo_bin("ghidra").unwrap();
cmd.arg("summary")
.arg("--project")
.arg(PROJECT_NAME)
.arg("--program")
.arg(PROGRAM_NAME)
.timeout(std::time::Duration::from_secs(300))
.assert()
.success()
.stdout(predicate::str::contains("Program Summary"));
}
}