use serial_test::serial;
use std::sync::OnceLock;
#[macro_use]
mod common;
use common::{ensure_test_project, get_function_address, ghidra, DaemonTestHarness};
const TEST_PROJECT: &str = "ci-test";
const TEST_PROGRAM: &str = "sample_binary";
static HARNESS: OnceLock<DaemonTestHarness> = OnceLock::new();
fn harness() -> &'static DaemonTestHarness {
HARNESS.get_or_init(|| {
ensure_test_project(TEST_PROJECT, TEST_PROGRAM);
DaemonTestHarness::new(TEST_PROJECT, TEST_PROGRAM).expect("Failed to start daemon")
})
}
#[test]
#[serial]
fn test_patch_bytes_success() {
require_ghidra!();
let harness = harness();
let main_addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main");
let result = ghidra(harness)
.arg("patch")
.arg("bytes")
.arg(&main_addr)
.arg("90909090") .arg("--program")
.arg(TEST_PROGRAM)
.run();
assert!(
result.exit_code == 0
|| result.stderr.contains("conflict")
|| result.stderr.contains("Memory change"),
"Expected success or instruction conflict, got: stderr={}",
result.stderr
);
}
#[test]
#[serial]
fn test_patch_nop_success() {
require_ghidra!();
let harness = harness();
let main_addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main");
let result = ghidra(harness)
.arg("patch")
.arg("nop")
.arg(&main_addr)
.arg("--program")
.arg(TEST_PROGRAM)
.run();
assert!(
result.exit_code == 0
|| result.stderr.contains("conflict")
|| result.stderr.contains("Memory change"),
"Expected success or instruction conflict, got: stderr={}",
result.stderr
);
}
#[test]
#[serial]
fn test_patch_export() {
require_ghidra!();
let harness = harness();
let output_path = format!("/tmp/ghidra-test-export-{}.bin", uuid::Uuid::new_v4());
let result = ghidra(harness)
.arg("patch")
.arg("export")
.arg("--output")
.arg(&output_path)
.arg("--program")
.arg(TEST_PROGRAM)
.run();
assert!(
result.exit_code == 0 || !result.stderr.is_empty(),
"Should either succeed or provide an error message"
);
let _ = std::fs::remove_file(&output_path);
}
#[test]
#[serial]
fn test_patch_at_function_boundary() {
require_ghidra!();
let harness = harness();
let func_addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main");
let result = ghidra(harness)
.arg("patch")
.arg("bytes")
.arg(&func_addr)
.arg("c3")
.arg("--program")
.arg(TEST_PROGRAM)
.run();
assert!(
result.exit_code == 0
|| result.stderr.contains("conflict")
|| result.stderr.contains("Memory change"),
"Expected success or instruction conflict error, got exit_code={}, stderr={}",
result.exit_code,
result.stderr
);
}
#[test]
#[serial]
fn test_patch_invalid_address_fails() {
require_ghidra!();
let harness = harness();
let result = ghidra(harness)
.arg("patch")
.arg("bytes")
.arg("0xffffffffffffffff") .arg("90")
.arg("--program")
.arg(TEST_PROGRAM)
.run();
result.assert_failure();
assert!(
result.stderr.to_lowercase().contains("error")
|| result.stderr.to_lowercase().contains("invalid")
|| result.stderr.to_lowercase().contains("address")
|| result.stdout.to_lowercase().contains("error"),
"Expected error message about invalid address.\nstderr: {}\nstdout: {}",
result.stderr,
result.stdout
);
}
#[test]
#[serial]
fn test_patch_invalid_hex_fails() {
require_ghidra!();
let harness = harness();
let main_addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main");
let result = ghidra(harness)
.arg("patch")
.arg("bytes")
.arg(&main_addr)
.arg("ZZZZ") .arg("--program")
.arg(TEST_PROGRAM)
.run();
result.assert_failure();
}
#[test]
#[serial]
fn test_patch_odd_hex_length() {
require_ghidra!();
let harness = harness();
let main_addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main");
let _result = ghidra(harness)
.arg("patch")
.arg("bytes")
.arg(&main_addr)
.arg("909") .arg("--program")
.arg(TEST_PROGRAM)
.run();
}
#[test]
#[serial]
fn test_patch_without_program_arg() {
require_ghidra!();
let harness = harness();
let main_addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main");
let result = ghidra(harness)
.arg("patch")
.arg("bytes")
.arg(&main_addr)
.arg("90")
.run();
assert!(
result.exit_code == 0 || !result.stderr.is_empty(),
"Should either succeed or provide an error message"
);
}
#[test]
#[serial]
fn test_patch_output_format_structure() {
require_ghidra!();
let harness = harness();
let main_addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main");
let result = ghidra(harness)
.arg("patch")
.arg("bytes")
.arg(&main_addr)
.arg("90")
.arg("--program")
.arg(TEST_PROGRAM)
.run();
assert!(
result.exit_code == 0 || !result.stderr.is_empty(),
"Should produce output (success or error message)"
);
}