//! Tests for type operations. use predicates::prelude::*; use serial_test::serial; use std::sync::OnceLock; #[macro_use] mod common; use common::{ensure_test_project, get_function_address, DaemonTestHarness}; const TEST_PROJECT: &str = "ci-test"; const TEST_PROGRAM: &str = "sample_binary"; static HARNESS: OnceLock = 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_type_list() { require_ghidra!(); let _harness = harness(); assert_cmd::cargo::cargo_bin_cmd!("ghidra") .arg("type") .arg("list") .arg("--project") .arg(TEST_PROJECT) .arg("--program") .arg(TEST_PROGRAM) .assert() .success(); } #[test] #[serial] fn test_type_get_primitive() { require_ghidra!(); let _harness = harness(); assert_cmd::cargo::cargo_bin_cmd!("ghidra") .arg("type") .arg("get") .arg("int") .arg("--project") .arg(TEST_PROJECT) .arg("--program") .arg(TEST_PROGRAM) .assert() .success() .stdout(predicate::str::contains("size")); } #[test] #[serial] fn test_type_create() { require_ghidra!(); let _harness = harness(); assert_cmd::cargo::cargo_bin_cmd!("ghidra") .arg("type") .arg("create") .arg("MyTestStruct") .arg("--project") .arg(TEST_PROJECT) .arg("--program") .arg(TEST_PROGRAM) .assert() .success(); // Verify created type exists assert_cmd::cargo::cargo_bin_cmd!("ghidra") .arg("type") .arg("get") .arg("MyTestStruct") .arg("--project") .arg(TEST_PROJECT) .arg("--program") .arg(TEST_PROGRAM) .assert() .success() .stdout(predicate::str::contains("MyTestStruct")); } #[test] #[serial] fn test_type_apply() { require_ghidra!(); let harness = harness(); let addr = get_function_address(harness, TEST_PROJECT, TEST_PROGRAM, "main"); let output = assert_cmd::cargo::cargo_bin_cmd!("ghidra") .arg("type") .arg("apply") .arg(&addr) .arg("int") .arg("--project") .arg(TEST_PROJECT) .arg("--program") .arg(TEST_PROGRAM) .output() .expect("Failed to run command"); // Applying a type at a code address may conflict with existing instructions let stderr = String::from_utf8_lossy(&output.stderr); assert!( output.status.success() || stderr.contains("Conflicting instruction") || stderr.contains("conflict"), "Expected success or instruction conflict, got: {}", stderr ); } #[test] #[serial] fn test_type_get_nonexistent() { require_ghidra!(); let _harness = harness(); assert_cmd::cargo::cargo_bin_cmd!("ghidra") .arg("type") .arg("get") .arg("NonexistentType12345") .arg("--project") .arg(TEST_PROJECT) .arg("--program") .arg(TEST_PROGRAM) .assert() .failure(); }