mirror of
https://github.com/encounter/ghidra-cli.git
synced 2026-07-10 03:18:56 -07:00
- 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>
236 lines
7.2 KiB
Rust
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"));
|
|
}
|
|
}
|